2024-01-01 19:58:21 +00:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2020-12-07 10:46:39 +00:00
2023-05-12 23:07:40 +00:00
use super ::analysis ::CodeActionData ;
2021-06-04 21:31:44 +00:00
use super ::code_lens ;
2021-03-15 22:01:41 +00:00
use super ::config ;
2022-03-02 21:06:38 +00:00
use super ::documents ::AssetOrDocument ;
2023-03-29 20:25:48 +00:00
use super ::documents ::DocumentsFilter ;
2021-02-06 12:39:01 +00:00
use super ::language_server ;
2020-12-21 13:44:26 +00:00
use super ::language_server ::StateSnapshot ;
2022-01-17 22:09:43 +00:00
use super ::performance ::Performance ;
2021-08-06 01:46:32 +00:00
use super ::refactor ::RefactorCodeActionData ;
use super ::refactor ::ALL_KNOWN_REFACTOR_ACTION_KINDS ;
use super ::refactor ::EXTRACT_CONSTANT ;
use super ::refactor ::EXTRACT_INTERFACE ;
use super ::refactor ::EXTRACT_TYPE ;
2021-11-23 00:08:56 +00:00
use super ::semantic_tokens ;
2021-04-20 01:26:36 +00:00
use super ::semantic_tokens ::SemanticTokensBuilder ;
2021-01-22 10:03:16 +00:00
use super ::text ::LineIndex ;
2023-03-23 14:23:04 +00:00
use super ::urls ::LspClientUrl ;
2022-01-19 22:10:14 +00:00
use super ::urls ::LspUrlMap ;
2021-07-25 05:33:42 +00:00
use super ::urls ::INVALID_SPECIFIER ;
2020-12-07 10:46:39 +00:00
2024-02-21 02:45:00 +00:00
use crate ::args ::jsr_url ;
2023-08-17 14:46:11 +00:00
use crate ::args ::FmtOptionsConfig ;
2023-07-08 20:06:45 +00:00
use crate ::cache ::HttpCache ;
use crate ::lsp ::cache ::CacheMetadata ;
use crate ::lsp ::documents ::Documents ;
2023-03-30 14:43:16 +00:00
use crate ::lsp ::logging ::lsp_warn ;
2020-12-15 19:34:39 +00:00
use crate ::tsc ;
2020-12-07 10:46:39 +00:00
use crate ::tsc ::ResolveArgs ;
2024-04-11 20:55:27 +00:00
use crate ::tsc ::MISSING_DEPENDENCY_SPECIFIER ;
2022-11-28 22:28:54 +00:00
use crate ::util ::path ::relative_specifier ;
2024-03-27 15:58:18 +00:00
use crate ::util ::path ::to_percent_decoded_str ;
2024-04-17 14:19:55 +00:00
use deno_runtime ::fs_util ::specifier_to_file_path ;
2020-12-07 10:46:39 +00:00
2023-09-29 19:44:59 +00:00
use dashmap ::DashMap ;
2023-09-19 15:37:27 +00:00
use deno_ast ::MediaType ;
2021-11-16 14:02:28 +00:00
use deno_core ::anyhow ::anyhow ;
2024-04-11 23:17:10 +00:00
use deno_core ::anyhow ::Context as _ ;
2020-12-07 10:46:39 +00:00
use deno_core ::error ::custom_error ;
use deno_core ::error ::AnyError ;
2023-12-22 01:04:02 +00:00
use deno_core ::futures ::FutureExt ;
2021-06-21 23:45:41 +00:00
use deno_core ::located_script_name ;
2023-09-12 11:14:45 +00:00
use deno_core ::op2 ;
2022-01-18 21:28:47 +00:00
use deno_core ::parking_lot ::Mutex ;
2021-02-17 18:47:18 +00:00
use deno_core ::resolve_url ;
2021-02-25 03:15:55 +00:00
use deno_core ::serde ::de ;
2020-12-07 10:46:39 +00:00
use deno_core ::serde ::Deserialize ;
2020-12-08 10:36:13 +00:00
use deno_core ::serde ::Serialize ;
2020-12-07 10:46:39 +00:00
use deno_core ::serde_json ;
use deno_core ::serde_json ::json ;
use deno_core ::serde_json ::Value ;
2023-12-01 21:57:52 +00:00
use deno_core ::serde_v8 ;
use deno_core ::v8 ;
2020-12-07 10:46:39 +00:00
use deno_core ::JsRuntime ;
use deno_core ::ModuleSpecifier ;
2022-03-14 17:44:15 +00:00
use deno_core ::OpState ;
2023-12-22 01:04:02 +00:00
use deno_core ::PollEventLoopOptions ;
2020-12-07 10:46:39 +00:00
use deno_core ::RuntimeOptions ;
2023-12-22 01:04:02 +00:00
use deno_runtime ::inspector_server ::InspectorServer ;
2021-10-21 11:05:43 +00:00
use deno_runtime ::tokio_util ::create_basic_runtime ;
2023-04-13 01:08:01 +00:00
use lazy_regex ::lazy_regex ;
2023-08-26 00:50:47 +00:00
use log ::error ;
2021-12-18 21:14:42 +00:00
use once_cell ::sync ::Lazy ;
2020-12-07 10:46:39 +00:00
use regex ::Captures ;
use regex ::Regex ;
2022-06-01 00:19:18 +00:00
use serde_repr ::Deserialize_repr ;
use serde_repr ::Serialize_repr ;
2021-10-10 21:26:22 +00:00
use std ::cmp ;
use std ::collections ::HashMap ;
2021-03-15 22:01:41 +00:00
use std ::collections ::HashSet ;
2023-12-22 01:04:02 +00:00
use std ::net ::SocketAddr ;
2023-05-12 23:07:40 +00:00
use std ::ops ::Range ;
2021-10-10 21:26:22 +00:00
use std ::path ::Path ;
2023-12-22 01:04:02 +00:00
use std ::rc ::Rc ;
2021-09-07 14:39:32 +00:00
use std ::sync ::Arc ;
2020-12-21 13:44:26 +00:00
use std ::thread ;
2021-10-10 21:26:22 +00:00
use text_size ::TextRange ;
use text_size ::TextSize ;
2020-12-21 13:44:26 +00:00
use tokio ::sync ::mpsc ;
2023-12-22 01:04:02 +00:00
use tokio ::sync ::mpsc ::UnboundedReceiver ;
2020-12-21 13:44:26 +00:00
use tokio ::sync ::oneshot ;
2022-02-02 14:25:22 +00:00
use tokio_util ::sync ::CancellationToken ;
2022-04-03 04:17:30 +00:00
use tower_lsp ::jsonrpc ::Error as LspError ;
use tower_lsp ::jsonrpc ::Result as LspResult ;
use tower_lsp ::lsp_types as lsp ;
2020-12-21 13:44:26 +00:00
2021-12-18 21:14:42 +00:00
static BRACKET_ACCESSOR_RE : Lazy < Regex > =
2023-04-13 01:08:01 +00:00
lazy_regex! ( r # "^\[['"](.+)[\['"]\]$"# ) ;
static CAPTION_RE : Lazy < Regex > =
lazy_regex! ( r "<caption>(.*?)</caption>\s*\r?\n((?:\s|\S)*)" ) ;
static CODEBLOCK_RE : Lazy < Regex > = lazy_regex! ( r "^\s*[~`]{3}" ) ;
static EMAIL_MATCH_RE : Lazy < Regex > = lazy_regex! ( r "(.+)\s<([-.\w]+@[-.\w]+)>" ) ;
static HTTP_RE : Lazy < Regex > = lazy_regex! ( r # "(?i)^https?:"# ) ;
static JSDOC_LINKS_RE : Lazy < Regex > = lazy_regex! (
r "(?i)\{@(link|linkplain|linkcode) (https?://[^ |}]+?)(?:[| ]([^{}\n]+?))?\}"
) ;
static PART_KIND_MODIFIER_RE : Lazy < Regex > = lazy_regex! ( r ",|\s+" ) ;
static PART_RE : Lazy < Regex > = lazy_regex! ( r "^(\S+)\s*-?\s*" ) ;
static SCOPE_RE : Lazy < Regex > = lazy_regex! ( r "scope_(\d)" ) ;
2021-09-16 02:07:52 +00:00
2021-03-15 22:01:41 +00:00
const FILE_EXTENSION_KIND_MODIFIERS : & [ & str ] =
& [ " .d.ts " , " .ts " , " .tsx " , " .js " , " .jsx " , " .json " ] ;
2020-12-21 13:44:26 +00:00
type Request = (
2023-10-02 06:32:05 +00:00
TscRequest ,
2021-11-18 18:50:24 +00:00
Arc < StateSnapshot > ,
2024-04-12 23:04:54 +00:00
oneshot ::Sender < Result < String , AnyError > > ,
2022-02-02 14:25:22 +00:00
CancellationToken ,
2024-04-23 15:50:30 +00:00
Option < PendingChange > ,
2020-12-21 13:44:26 +00:00
) ;
2023-10-17 01:51:42 +00:00
#[ derive(Debug, Clone, Copy, Serialize_repr) ]
#[ repr(u8) ]
pub enum IndentStyle {
#[ allow(dead_code) ]
None = 0 ,
Block = 1 ,
#[ allow(dead_code) ]
Smart = 2 ,
}
/// Relevant subset of https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6658.
2023-08-17 14:46:11 +00:00
#[ derive(Clone, Debug, Default, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct FormatCodeSettings {
2023-10-17 01:51:42 +00:00
base_indent_size : Option < u8 > ,
2023-08-17 14:46:11 +00:00
indent_size : Option < u8 > ,
2023-10-17 01:51:42 +00:00
tab_size : Option < u8 > ,
new_line_character : Option < String > ,
convert_tabs_to_spaces : Option < bool > ,
indent_style : Option < IndentStyle > ,
trim_trailing_whitespace : Option < bool > ,
insert_space_after_comma_delimiter : Option < bool > ,
insert_space_after_semicolon_in_for_statements : Option < bool > ,
insert_space_before_and_after_binary_operators : Option < bool > ,
insert_space_after_constructor : Option < bool > ,
insert_space_after_keywords_in_control_flow_statements : Option < bool > ,
insert_space_after_function_keyword_for_anonymous_functions : Option < bool > ,
insert_space_after_opening_and_before_closing_nonempty_parenthesis :
Option < bool > ,
insert_space_after_opening_and_before_closing_nonempty_brackets : Option < bool > ,
insert_space_after_opening_and_before_closing_nonempty_braces : Option < bool > ,
insert_space_after_opening_and_before_closing_template_string_braces :
Option < bool > ,
insert_space_after_opening_and_before_closing_jsx_expression_braces :
Option < bool > ,
insert_space_after_type_assertion : Option < bool > ,
insert_space_before_function_parenthesis : Option < bool > ,
place_open_brace_on_new_line_for_functions : Option < bool > ,
place_open_brace_on_new_line_for_control_blocks : Option < bool > ,
insert_space_before_type_annotation : Option < bool > ,
indent_multi_line_object_literal_beginning_on_blank_line : Option < bool > ,
2023-08-17 14:46:11 +00:00
semicolons : Option < SemicolonPreference > ,
2023-10-17 01:51:42 +00:00
indent_switch_case : Option < bool > ,
2023-08-17 14:46:11 +00:00
}
impl From < & FmtOptionsConfig > for FormatCodeSettings {
fn from ( config : & FmtOptionsConfig ) -> Self {
FormatCodeSettings {
2023-10-17 01:51:42 +00:00
base_indent_size : Some ( 0 ) ,
2023-08-17 14:46:11 +00:00
indent_size : Some ( config . indent_width . unwrap_or ( 2 ) ) ,
2023-10-17 01:51:42 +00:00
tab_size : Some ( config . indent_width . unwrap_or ( 2 ) ) ,
new_line_character : Some ( " \n " . to_string ( ) ) ,
convert_tabs_to_spaces : Some ( ! config . use_tabs . unwrap_or ( false ) ) ,
indent_style : Some ( IndentStyle ::Block ) ,
trim_trailing_whitespace : Some ( false ) ,
insert_space_after_comma_delimiter : Some ( true ) ,
insert_space_after_semicolon_in_for_statements : Some ( true ) ,
insert_space_before_and_after_binary_operators : Some ( true ) ,
insert_space_after_constructor : Some ( false ) ,
insert_space_after_keywords_in_control_flow_statements : Some ( true ) ,
insert_space_after_function_keyword_for_anonymous_functions : Some ( true ) ,
insert_space_after_opening_and_before_closing_nonempty_parenthesis : Some (
false ,
) ,
insert_space_after_opening_and_before_closing_nonempty_brackets : Some (
false ,
) ,
insert_space_after_opening_and_before_closing_nonempty_braces : Some ( true ) ,
insert_space_after_opening_and_before_closing_template_string_braces :
Some ( false ) ,
insert_space_after_opening_and_before_closing_jsx_expression_braces : Some (
false ,
) ,
insert_space_after_type_assertion : Some ( false ) ,
insert_space_before_function_parenthesis : Some ( false ) ,
place_open_brace_on_new_line_for_functions : Some ( false ) ,
place_open_brace_on_new_line_for_control_blocks : Some ( false ) ,
insert_space_before_type_annotation : Some ( false ) ,
indent_multi_line_object_literal_beginning_on_blank_line : Some ( false ) ,
2023-08-17 14:46:11 +00:00
semicolons : match config . semi_colons {
Some ( false ) = > Some ( SemicolonPreference ::Remove ) ,
_ = > Some ( SemicolonPreference ::Insert ) ,
} ,
2023-10-17 01:51:42 +00:00
indent_switch_case : Some ( true ) ,
2023-08-17 14:46:11 +00:00
}
}
}
#[ derive(Clone, Debug, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub enum SemicolonPreference {
Insert ,
Remove ,
}
2023-09-29 19:44:59 +00:00
fn normalize_diagnostic (
diagnostic : & mut crate ::tsc ::Diagnostic ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
if let Some ( file_name ) = & mut diagnostic . file_name {
* file_name = specifier_map . normalize ( & file_name ) ? . to_string ( ) ;
}
for ri in diagnostic . related_information . iter_mut ( ) . flatten ( ) {
normalize_diagnostic ( ri , specifier_map ) ? ;
}
Ok ( ( ) )
}
pub struct TsServer {
2023-11-29 21:18:23 +00:00
performance : Arc < Performance > ,
2023-12-22 01:04:02 +00:00
cache : Arc < dyn HttpCache > ,
2023-09-29 19:44:59 +00:00
sender : mpsc ::UnboundedSender < Request > ,
2023-12-22 01:04:02 +00:00
receiver : Mutex < Option < mpsc ::UnboundedReceiver < Request > > > ,
2023-09-29 19:44:59 +00:00
specifier_map : Arc < TscSpecifierMap > ,
2023-12-22 01:04:02 +00:00
inspector_server : Mutex < Option < Arc < InspectorServer > > > ,
2024-04-22 15:03:16 +00:00
pending_change : Mutex < Option < PendingChange > > ,
2023-12-22 01:04:02 +00:00
}
impl std ::fmt ::Debug for TsServer {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
f . debug_struct ( " TsServer " )
. field ( " performance " , & self . performance )
. field ( " cache " , & self . cache )
. field ( " sender " , & self . sender )
. field ( " receiver " , & self . receiver )
. field ( " specifier_map " , & self . specifier_map )
. field ( " inspector_server " , & self . inspector_server . lock ( ) . is_some ( ) )
. finish ( )
}
2023-09-29 19:44:59 +00:00
}
2020-12-21 13:44:26 +00:00
2024-04-23 22:29:29 +00:00
#[ derive(Debug, Clone, Copy, PartialEq) ]
2024-04-11 01:06:37 +00:00
#[ repr(u8) ]
pub enum ChangeKind {
Opened = 0 ,
Modified = 1 ,
Closed = 2 ,
}
impl Serialize for ChangeKind {
fn serialize < S > ( & self , serializer : S ) -> Result < S ::Ok , S ::Error >
where
S : serde ::Serializer ,
{
serializer . serialize_i32 ( * self as i32 )
}
}
2024-04-23 22:29:29 +00:00
#[ derive(Debug, PartialEq) ]
2024-04-22 15:03:16 +00:00
pub struct PendingChange {
pub modified_scripts : Vec < ( String , ChangeKind ) > ,
pub project_version : usize ,
pub config_changed : bool ,
}
impl PendingChange {
2024-04-23 15:50:30 +00:00
fn to_v8 < ' s > (
& self ,
scope : & mut v8 ::HandleScope < ' s > ,
) -> Result < v8 ::Local < ' s , v8 ::Value > , AnyError > {
let modified_scripts = serde_v8 ::to_v8 ( scope , & self . modified_scripts ) ? ;
let project_version =
v8 ::Integer ::new_from_unsigned ( scope , self . project_version as u32 ) . into ( ) ;
let config_changed = v8 ::Boolean ::new ( scope , self . config_changed ) . into ( ) ;
Ok (
v8 ::Array ::new_with_elements (
scope ,
& [ modified_scripts , project_version , config_changed ] ,
)
. into ( ) ,
)
2024-04-22 15:03:16 +00:00
}
fn coalesce (
& mut self ,
new_version : usize ,
modified_scripts : Vec < ( String , ChangeKind ) > ,
config_changed : bool ,
) {
2024-04-23 22:29:29 +00:00
use ChangeKind ::* ;
2024-04-22 15:03:16 +00:00
self . project_version = self . project_version . max ( new_version ) ;
self . config_changed | = config_changed ;
for ( spec , new ) in modified_scripts {
if let Some ( ( _ , current ) ) =
self . modified_scripts . iter_mut ( ) . find ( | ( s , _ ) | s = = & spec )
{
2024-04-23 22:29:29 +00:00
// already a pending change for this specifier,
// coalesce the change kinds
2024-04-22 15:03:16 +00:00
match ( * current , new ) {
2024-04-23 22:29:29 +00:00
( _ , Closed ) = > {
* current = Closed ;
2024-04-22 15:03:16 +00:00
}
2024-04-23 22:29:29 +00:00
( Opened | Closed , Opened ) = > {
* current = Opened ;
}
( Modified , Opened ) = > {
lsp_warn! ( " Unexpected change from Modified -> Opened " ) ;
* current = Opened ;
}
( Opened , Modified ) = > {
// Opening may change the set of files in the project
* current = Opened ;
}
( Closed , Modified ) = > {
lsp_warn! ( " Unexpected change from Closed -> Modifed " ) ;
// Shouldn't happen, but if it does treat it as closed
// since it's "stronger" than modifying an open doc
* current = Closed ;
}
( Modified , Modified ) = > {
// no change
2024-04-22 15:03:16 +00:00
}
}
2024-04-23 22:29:29 +00:00
} else {
self . modified_scripts . push ( ( spec , new ) ) ;
2024-04-22 15:03:16 +00:00
}
}
}
}
2020-12-21 13:44:26 +00:00
impl TsServer {
2024-04-02 22:02:50 +00:00
pub fn new ( performance : Arc < Performance > , cache : Arc < dyn HttpCache > ) -> Self {
2023-12-22 01:04:02 +00:00
let ( tx , request_rx ) = mpsc ::unbounded_channel ::< Request > ( ) ;
2023-09-29 19:44:59 +00:00
Self {
2023-11-29 21:18:23 +00:00
performance ,
2023-12-22 01:04:02 +00:00
cache ,
2023-09-29 19:44:59 +00:00
sender : tx ,
2023-12-22 01:04:02 +00:00
receiver : Mutex ::new ( Some ( request_rx ) ) ,
specifier_map : Arc ::new ( TscSpecifierMap ::new ( ) ) ,
inspector_server : Mutex ::new ( None ) ,
2024-04-22 15:03:16 +00:00
pending_change : Mutex ::new ( None ) ,
2023-09-29 19:44:59 +00:00
}
2020-12-21 13:44:26 +00:00
}
2024-04-11 23:17:10 +00:00
pub fn start (
& self ,
inspector_server_addr : Option < String > ,
) -> Result < ( ) , AnyError > {
let maybe_inspector_server = match inspector_server_addr {
Some ( addr ) = > {
let addr : SocketAddr = addr . parse ( ) . with_context ( | | {
format! ( " Invalid inspector server address \" {} \" " , & addr )
} ) ? ;
let server = InspectorServer ::new ( addr , " deno-lsp-tsc " ) ? ;
Some ( Arc ::new ( server ) )
}
None = > None ,
} ;
2023-12-22 01:04:02 +00:00
* self . inspector_server . lock ( ) = maybe_inspector_server . clone ( ) ;
// TODO(bartlomieju): why is the join_handle ignored here? Should we store it
// on the `TsServer` struct.
let receiver = self . receiver . lock ( ) . take ( ) . unwrap ( ) ;
let performance = self . performance . clone ( ) ;
let cache = self . cache . clone ( ) ;
let specifier_map = self . specifier_map . clone ( ) ;
let _join_handle = thread ::spawn ( move | | {
run_tsc_thread (
receiver ,
performance . clone ( ) ,
cache . clone ( ) ,
specifier_map . clone ( ) ,
maybe_inspector_server ,
)
} ) ;
2024-04-11 23:17:10 +00:00
Ok ( ( ) )
2023-12-22 01:04:02 +00:00
}
2024-04-22 15:03:16 +00:00
pub fn project_changed < ' a > (
2024-04-11 01:06:37 +00:00
& self ,
snapshot : Arc < StateSnapshot > ,
2024-04-22 15:03:16 +00:00
modified_scripts : impl IntoIterator < Item = ( & ' a ModuleSpecifier , ChangeKind ) > ,
2024-04-11 01:06:37 +00:00
config_changed : bool ,
) {
2024-04-11 18:33:03 +00:00
let modified_scripts = modified_scripts
2024-04-22 15:03:16 +00:00
. into_iter ( )
2024-04-11 18:33:03 +00:00
. map ( | ( spec , change ) | ( self . specifier_map . denormalize ( spec ) , change ) )
. collect ::< Vec < _ > > ( ) ;
2024-04-22 15:03:16 +00:00
match & mut * self . pending_change . lock ( ) {
Some ( pending_change ) = > {
pending_change . coalesce (
snapshot . project_version ,
modified_scripts ,
config_changed ,
) ;
}
pending = > {
let pending_change = PendingChange {
modified_scripts ,
project_version : snapshot . project_version ,
config_changed ,
} ;
* pending = Some ( pending_change ) ;
}
}
2024-04-11 01:06:37 +00:00
}
2023-05-12 23:07:40 +00:00
pub async fn get_diagnostics (
& self ,
snapshot : Arc < StateSnapshot > ,
specifiers : Vec < ModuleSpecifier > ,
token : CancellationToken ,
) -> Result < HashMap < String , Vec < crate ::tsc ::Diagnostic > > , AnyError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetDiagnostics ( (
specifiers
. into_iter ( )
. map ( | s | self . specifier_map . denormalize ( & s ) )
. collect ::< Vec < String > > ( ) ,
snapshot . project_version ,
) ) ;
2023-11-24 22:35:33 +00:00
let raw_diagnostics = self . request_with_cancellation ::< HashMap < String , Vec < crate ::tsc ::Diagnostic > > > ( snapshot , req , token ) . await ? ;
let mut diagnostics_map = HashMap ::with_capacity ( raw_diagnostics . len ( ) ) ;
for ( mut specifier , mut diagnostics ) in raw_diagnostics {
2023-09-29 19:44:59 +00:00
specifier = self . specifier_map . normalize ( & specifier ) ? . to_string ( ) ;
for diagnostic in & mut diagnostics {
normalize_diagnostic ( diagnostic , & self . specifier_map ) ? ;
}
diagnostics_map . insert ( specifier , diagnostics ) ;
}
Ok ( diagnostics_map )
2023-05-12 23:07:40 +00:00
}
2024-04-17 20:40:42 +00:00
pub async fn cleanup_semantic_cache ( & self , snapshot : Arc < StateSnapshot > ) {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::CleanupSemanticCache ;
2024-04-17 20:40:42 +00:00
self
. request ::< ( ) > ( snapshot , req )
. await
. map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
. ok ( ) ;
}
2023-05-12 23:07:40 +00:00
pub async fn find_references (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
) -> Result < Option < Vec < ReferencedSymbol > > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::FindReferences ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< Option < Vec < ReferencedSymbol > > > ( snapshot , req )
. await
. and_then ( | mut symbols | {
for symbol in symbols . iter_mut ( ) . flatten ( ) {
symbol . normalize ( & self . specifier_map ) ? ;
}
Ok ( symbols )
} )
. map_err ( | err | {
log ::error! ( " Unable to get references from TypeScript: {} " , err ) ;
LspError ::internal_error ( )
} )
2023-05-12 23:07:40 +00:00
}
pub async fn get_navigation_tree (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
) -> Result < NavigationTree , AnyError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetNavigationTree ( ( self
. specifier_map
. denormalize ( & specifier ) , ) ) ;
2023-10-02 06:32:05 +00:00
self . request ( snapshot , req ) . await
2023-05-12 23:07:40 +00:00
}
pub async fn get_supported_code_fixes (
& self ,
snapshot : Arc < StateSnapshot > ,
) -> Result < Vec < String > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetSupportedCodeFixes ;
2023-10-02 06:32:05 +00:00
self . request ( snapshot , req ) . await . map_err ( | err | {
log ::error! ( " Unable to get fixable diagnostics: {} " , err ) ;
LspError ::internal_error ( )
} )
2023-05-12 23:07:40 +00:00
}
pub async fn get_quick_info (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
) -> Result < Option < QuickInfo > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetQuickInfoAtPosition ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
) ) ;
2023-05-12 23:07:40 +00:00
self . request ( snapshot , req ) . await . map_err ( | err | {
log ::error! ( " Unable to get quick info: {} " , err ) ;
LspError ::internal_error ( )
} )
}
pub async fn get_code_fixes (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
range : Range < u32 > ,
codes : Vec < String > ,
2023-08-17 14:46:11 +00:00
format_code_settings : FormatCodeSettings ,
2023-09-18 19:48:32 +00:00
preferences : UserPreferences ,
2023-05-12 23:07:40 +00:00
) -> Vec < CodeFixAction > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetCodeFixesAtPosition ( Box ::new ( (
self . specifier_map . denormalize ( & specifier ) ,
range . start ,
range . end ,
codes ,
format_code_settings ,
preferences ,
) ) ) ;
2023-09-29 19:44:59 +00:00
let result = self
. request ::< Vec < CodeFixAction > > ( snapshot , req )
. await
. and_then ( | mut actions | {
for action in & mut actions {
action . normalize ( & self . specifier_map ) ? ;
}
Ok ( actions )
} ) ;
match result {
2023-05-12 23:07:40 +00:00
Ok ( items ) = > items ,
Err ( err ) = > {
// sometimes tsc reports errors when retrieving code actions
// because they don't reflect the current state of the document
// so we will log them to the output, but we won't send an error
// message back to the client.
log ::error! ( " Error getting actions from TypeScript: {} " , err ) ;
Vec ::new ( )
}
}
}
pub async fn get_applicable_refactors (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
range : Range < u32 > ,
2023-09-18 19:48:32 +00:00
preferences : Option < UserPreferences > ,
2024-04-20 20:18:43 +00:00
trigger_kind : Option < lsp ::CodeActionTriggerKind > ,
2023-05-12 23:07:40 +00:00
only : String ,
) -> Result < Vec < ApplicableRefactorInfo > , LspError > {
2024-04-23 15:50:30 +00:00
let trigger_kind = trigger_kind . map ( | reason | match reason {
2024-04-20 20:18:43 +00:00
lsp ::CodeActionTriggerKind ::INVOKED = > " invoked " ,
lsp ::CodeActionTriggerKind ::AUTOMATIC = > " implicit " ,
_ = > unreachable! ( ) ,
} ) ;
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetApplicableRefactors ( Box ::new ( (
self . specifier_map . denormalize ( & specifier ) ,
range . into ( ) ,
preferences . unwrap_or_default ( ) ,
trigger_kind ,
only ,
) ) ) ;
2023-05-12 23:07:40 +00:00
self . request ( snapshot , req ) . await . map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
}
pub async fn get_combined_code_fix (
& self ,
snapshot : Arc < StateSnapshot > ,
code_action_data : & CodeActionData ,
2023-08-17 14:46:11 +00:00
format_code_settings : FormatCodeSettings ,
2023-09-18 19:48:32 +00:00
preferences : UserPreferences ,
2023-05-12 23:07:40 +00:00
) -> Result < CombinedCodeActions , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetCombinedCodeFix ( Box ::new ( (
CombinedCodeFixScope {
r#type : " file " ,
file_name : self . specifier_map . denormalize ( & code_action_data . specifier ) ,
} ,
code_action_data . fix_id . clone ( ) ,
format_code_settings ,
preferences ,
) ) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< CombinedCodeActions > ( snapshot , req )
. await
. and_then ( | mut actions | {
actions . normalize ( & self . specifier_map ) ? ;
Ok ( actions )
} )
. map_err ( | err | {
log ::error! ( " Unable to get combined fix from TypeScript: {} " , err ) ;
LspError ::internal_error ( )
} )
2023-05-12 23:07:40 +00:00
}
2023-09-18 19:48:32 +00:00
#[ allow(clippy::too_many_arguments) ]
2023-05-12 23:07:40 +00:00
pub async fn get_edits_for_refactor (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
2023-08-17 14:46:11 +00:00
format_code_settings : FormatCodeSettings ,
2023-05-12 23:07:40 +00:00
range : Range < u32 > ,
refactor_name : String ,
action_name : String ,
2023-09-18 19:48:32 +00:00
preferences : Option < UserPreferences > ,
2023-05-12 23:07:40 +00:00
) -> Result < RefactorEditInfo , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetEditsForRefactor ( Box ::new ( (
self . specifier_map . denormalize ( & specifier ) ,
format_code_settings ,
range . into ( ) ,
refactor_name ,
action_name ,
preferences ,
) ) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< RefactorEditInfo > ( snapshot , req )
. await
. and_then ( | mut info | {
info . normalize ( & self . specifier_map ) ? ;
Ok ( info )
} )
. map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
2023-05-12 23:07:40 +00:00
}
2023-08-26 00:50:47 +00:00
pub async fn get_edits_for_file_rename (
& self ,
snapshot : Arc < StateSnapshot > ,
old_specifier : ModuleSpecifier ,
new_specifier : ModuleSpecifier ,
format_code_settings : FormatCodeSettings ,
user_preferences : UserPreferences ,
) -> Result < Vec < FileTextChanges > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetEditsForFileRename ( Box ::new ( (
self . specifier_map . denormalize ( & old_specifier ) ,
self . specifier_map . denormalize ( & new_specifier ) ,
format_code_settings ,
user_preferences ,
) ) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< Vec < FileTextChanges > > ( snapshot , req )
. await
. and_then ( | mut changes | {
for changes in & mut changes {
changes . normalize ( & self . specifier_map ) ? ;
2024-03-27 15:58:18 +00:00
for text_changes in & mut changes . text_changes {
text_changes . new_text =
to_percent_decoded_str ( & text_changes . new_text ) ;
}
2023-09-29 19:44:59 +00:00
}
Ok ( changes )
} )
. map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
2023-08-26 00:50:47 +00:00
}
2023-05-12 23:07:40 +00:00
pub async fn get_document_highlights (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
files_to_search : Vec < ModuleSpecifier > ,
) -> Result < Option < Vec < DocumentHighlights > > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetDocumentHighlights ( Box ::new ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
files_to_search
. into_iter ( )
. map ( | s | self . specifier_map . denormalize ( & s ) )
. collect ::< Vec < _ > > ( ) ,
) ) ) ;
2023-05-12 23:07:40 +00:00
self . request ( snapshot , req ) . await . map_err ( | err | {
log ::error! ( " Unable to get document highlights from TypeScript: {} " , err ) ;
LspError ::internal_error ( )
} )
}
pub async fn get_definition (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
) -> Result < Option < DefinitionInfoAndBoundSpan > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetDefinitionAndBoundSpan ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< Option < DefinitionInfoAndBoundSpan > > ( snapshot , req )
. await
. and_then ( | mut info | {
if let Some ( info ) = & mut info {
info . normalize ( & self . specifier_map ) ? ;
}
Ok ( info )
} )
. map_err ( | err | {
log ::error! ( " Unable to get definition from TypeScript: {} " , err ) ;
LspError ::internal_error ( )
} )
2023-05-12 23:07:40 +00:00
}
pub async fn get_type_definition (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
) -> Result < Option < Vec < DefinitionInfo > > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetTypeDefinitionAtPosition ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< Option < Vec < DefinitionInfo > > > ( snapshot , req )
. await
. and_then ( | mut infos | {
for info in infos . iter_mut ( ) . flatten ( ) {
info . normalize ( & self . specifier_map ) ? ;
}
Ok ( infos )
} )
. map_err ( | err | {
log ::error! ( " Unable to get type definition from TypeScript: {} " , err ) ;
LspError ::internal_error ( )
} )
2023-05-12 23:07:40 +00:00
}
pub async fn get_completions (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
options : GetCompletionsAtPositionOptions ,
2023-08-17 14:46:11 +00:00
format_code_settings : FormatCodeSettings ,
2023-05-12 23:07:40 +00:00
) -> Option < CompletionInfo > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetCompletionsAtPosition ( Box ::new ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
options ,
format_code_settings ,
) ) ) ;
2023-05-12 23:07:40 +00:00
match self . request ( snapshot , req ) . await {
Ok ( maybe_info ) = > maybe_info ,
Err ( err ) = > {
log ::error! ( " Unable to get completion info from TypeScript: {:#} " , err ) ;
None
}
}
}
pub async fn get_completion_details (
& self ,
snapshot : Arc < StateSnapshot > ,
args : GetCompletionDetailsArgs ,
) -> Result < Option < CompletionEntryDetails > , AnyError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetCompletionEntryDetails ( Box ::new ( (
self . specifier_map . denormalize ( & args . specifier ) ,
args . position ,
args . name ,
args . format_code_settings . unwrap_or_default ( ) ,
args . source ,
args . preferences ,
args . data ,
) ) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< Option < CompletionEntryDetails > > ( snapshot , req )
. await
. and_then ( | mut details | {
if let Some ( details ) = & mut details {
details . normalize ( & self . specifier_map ) ? ;
}
Ok ( details )
} )
2023-05-12 23:07:40 +00:00
}
pub async fn get_implementations (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
) -> Result < Option < Vec < ImplementationLocation > > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetImplementationAtPosition ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< Option < Vec < ImplementationLocation > > > ( snapshot , req )
. await
. and_then ( | mut locations | {
for location in locations . iter_mut ( ) . flatten ( ) {
location . normalize ( & self . specifier_map ) ? ;
}
Ok ( locations )
} )
. map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
2023-05-12 23:07:40 +00:00
}
pub async fn get_outlining_spans (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
) -> Result < Vec < OutliningSpan > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetOutliningSpans ( ( self
. specifier_map
. denormalize ( & specifier ) , ) ) ;
2023-05-12 23:07:40 +00:00
self . request ( snapshot , req ) . await . map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
}
pub async fn provide_call_hierarchy_incoming_calls (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
) -> Result < Vec < CallHierarchyIncomingCall > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::ProvideCallHierarchyIncomingCalls ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< Vec < CallHierarchyIncomingCall > > ( snapshot , req )
. await
. and_then ( | mut calls | {
for call in & mut calls {
call . normalize ( & self . specifier_map ) ? ;
}
Ok ( calls )
} )
. map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
2023-05-12 23:07:40 +00:00
}
pub async fn provide_call_hierarchy_outgoing_calls (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
) -> Result < Vec < CallHierarchyOutgoingCall > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::ProvideCallHierarchyOutgoingCalls ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< Vec < CallHierarchyOutgoingCall > > ( snapshot , req )
. await
. and_then ( | mut calls | {
for call in & mut calls {
call . normalize ( & self . specifier_map ) ? ;
}
Ok ( calls )
} )
. map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
2023-05-12 23:07:40 +00:00
}
pub async fn prepare_call_hierarchy (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
) -> Result < Option < OneOrMany < CallHierarchyItem > > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::PrepareCallHierarchy ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< Option < OneOrMany < CallHierarchyItem > > > ( snapshot , req )
. await
. and_then ( | mut items | {
match & mut items {
Some ( OneOrMany ::One ( item ) ) = > {
item . normalize ( & self . specifier_map ) ? ;
}
Some ( OneOrMany ::Many ( items ) ) = > {
for item in items {
item . normalize ( & self . specifier_map ) ? ;
}
}
None = > { }
}
Ok ( items )
} )
. map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
2023-05-12 23:07:40 +00:00
}
pub async fn find_rename_locations (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
) -> Result < Option < Vec < RenameLocation > > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::FindRenameLocations ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
false ,
false ,
false ,
) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< Option < Vec < RenameLocation > > > ( snapshot , req )
. await
. and_then ( | mut locations | {
for location in locations . iter_mut ( ) . flatten ( ) {
location . normalize ( & self . specifier_map ) ? ;
}
Ok ( locations )
} )
. map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
2023-05-12 23:07:40 +00:00
}
pub async fn get_smart_selection_range (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
) -> Result < SelectionRange , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetSmartSelectionRange ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
) ) ;
2023-05-12 23:07:40 +00:00
self . request ( snapshot , req ) . await . map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
}
pub async fn get_encoded_semantic_classifications (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
range : Range < u32 > ,
) -> Result < Classifications , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetEncodedSemanticClassifications ( (
self . specifier_map . denormalize ( & specifier ) ,
TextSpan {
start : range . start ,
length : range . end - range . start ,
} ,
" 2020 " ,
) ) ;
2023-05-12 23:07:40 +00:00
self . request ( snapshot , req ) . await . map_err ( | err | {
log ::error! ( " Failed to request to tsserver {} " , err ) ;
LspError ::invalid_request ( )
} )
}
pub async fn get_signature_help_items (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
position : u32 ,
options : SignatureHelpItemsOptions ,
) -> Result < Option < SignatureHelpItems > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetSignatureHelpItems ( (
self . specifier_map . denormalize ( & specifier ) ,
position ,
options ,
) ) ;
2023-05-12 23:07:40 +00:00
self . request ( snapshot , req ) . await . map_err ( | err | {
log ::error! ( " Failed to request to tsserver: {} " , err ) ;
LspError ::invalid_request ( )
} )
}
pub async fn get_navigate_to_items (
& self ,
snapshot : Arc < StateSnapshot > ,
args : GetNavigateToItemsArgs ,
) -> Result < Vec < NavigateToItem > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetNavigateToItems ( (
args . search ,
args . max_result_count ,
args . file . map ( | f | match resolve_url ( & f ) {
Ok ( s ) = > self . specifier_map . denormalize ( & s ) ,
Err ( _ ) = > f ,
} ) ,
) ) ;
2023-09-29 19:44:59 +00:00
self
. request ::< Vec < NavigateToItem > > ( snapshot , req )
. await
. and_then ( | mut items | {
for items in & mut items {
items . normalize ( & self . specifier_map ) ? ;
}
Ok ( items )
} )
. map_err ( | err | {
log ::error! ( " Failed request to tsserver: {} " , err ) ;
LspError ::invalid_request ( )
} )
2023-05-12 23:07:40 +00:00
}
pub async fn provide_inlay_hints (
& self ,
snapshot : Arc < StateSnapshot > ,
specifier : ModuleSpecifier ,
text_span : TextSpan ,
user_preferences : UserPreferences ,
) -> Result < Option < Vec < InlayHint > > , LspError > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::ProvideInlayHints ( (
self . specifier_map . denormalize ( & specifier ) ,
text_span ,
user_preferences ,
) ) ;
2023-05-12 23:07:40 +00:00
self . request ( snapshot , req ) . await . map_err ( | err | {
log ::error! ( " Unable to get inlay hints: {} " , err ) ;
LspError ::internal_error ( )
} )
}
async fn request < R > (
2020-12-21 13:44:26 +00:00
& self ,
2021-11-18 18:50:24 +00:00
snapshot : Arc < StateSnapshot > ,
2023-10-02 06:32:05 +00:00
req : TscRequest ,
2021-02-25 03:15:55 +00:00
) -> Result < R , AnyError >
2022-02-02 14:25:22 +00:00
where
R : de ::DeserializeOwned ,
{
2024-04-23 15:50:30 +00:00
let mark = self
. performance
. mark ( format! ( " tsc.request. {} " , req . method ( ) ) ) ;
2023-11-29 21:18:23 +00:00
let r = self
2022-02-02 14:25:22 +00:00
. request_with_cancellation ( snapshot , req , Default ::default ( ) )
2023-11-29 21:18:23 +00:00
. await ;
self . performance . measure ( mark ) ;
r
2022-02-02 14:25:22 +00:00
}
2023-05-12 23:07:40 +00:00
async fn request_with_cancellation < R > (
2022-02-02 14:25:22 +00:00
& self ,
snapshot : Arc < StateSnapshot > ,
2023-10-02 06:32:05 +00:00
req : TscRequest ,
2022-02-02 14:25:22 +00:00
token : CancellationToken ,
) -> Result < R , AnyError >
2021-02-25 03:15:55 +00:00
where
R : de ::DeserializeOwned ,
{
2023-11-30 03:35:39 +00:00
// When an LSP request is cancelled by the client, the future this is being
// executed under and any local variables here will be dropped at the next
// await point. To pass on that cancellation to the TS thread, we make this
// wrapper which cancels the request's token on drop.
struct DroppableToken ( CancellationToken ) ;
impl Drop for DroppableToken {
fn drop ( & mut self ) {
self . 0. cancel ( ) ;
}
}
let token = token . child_token ( ) ;
let droppable_token = DroppableToken ( token . clone ( ) ) ;
2024-04-12 23:04:54 +00:00
let ( tx , rx ) = oneshot ::channel ::< Result < String , AnyError > > ( ) ;
2024-04-22 15:03:16 +00:00
let change = self . pending_change . lock ( ) . take ( ) ;
if self
. sender
2024-04-23 15:50:30 +00:00
. send ( ( req , snapshot , tx , token , change ) )
2024-04-22 15:03:16 +00:00
. is_err ( )
{
2020-12-21 13:44:26 +00:00
return Err ( anyhow! ( " failed to send request to tsc thread " ) ) ;
}
2023-03-30 16:15:21 +00:00
let value = rx . await ? ? ;
2023-11-30 03:35:39 +00:00
drop ( droppable_token ) ;
2024-04-12 23:04:54 +00:00
Ok ( serde_json ::from_str ( & value ) ? )
2023-03-30 16:15:21 +00:00
}
2020-12-21 13:44:26 +00:00
}
2020-12-07 10:46:39 +00:00
2024-04-06 14:36:43 +00:00
/// An lsp representation of an asset in memory, that has either been retrieved
/// from static assets built into Rust, or static assets built into tsc.
2021-11-12 16:42:04 +00:00
#[ derive(Debug, Clone) ]
2024-04-06 14:36:43 +00:00
pub struct AssetDocument {
2022-03-02 21:06:38 +00:00
specifier : ModuleSpecifier ,
2022-05-20 20:40:55 +00:00
text : Arc < str > ,
2021-11-12 16:42:04 +00:00
line_index : Arc < LineIndex > ,
maybe_navigation_tree : Option < Arc < NavigationTree > > ,
}
2021-02-12 11:49:42 +00:00
impl AssetDocument {
2022-03-02 21:06:38 +00:00
pub fn new ( specifier : ModuleSpecifier , text : impl AsRef < str > ) -> Self {
2021-02-12 11:49:42 +00:00
let text = text . as_ref ( ) ;
2024-04-06 14:36:43 +00:00
Self {
2022-03-02 21:06:38 +00:00
specifier ,
2022-05-20 20:40:55 +00:00
text : text . into ( ) ,
2021-10-28 23:56:01 +00:00
line_index : Arc ::new ( LineIndex ::new ( text ) ) ,
2021-06-04 21:31:44 +00:00
maybe_navigation_tree : None ,
2024-04-06 14:36:43 +00:00
}
2021-11-12 16:42:04 +00:00
}
2022-03-02 21:06:38 +00:00
pub fn specifier ( & self ) -> & ModuleSpecifier {
2024-04-06 14:36:43 +00:00
& self . specifier
2022-03-02 21:06:38 +00:00
}
2024-04-06 14:36:43 +00:00
pub fn with_navigation_tree ( & self , tree : Arc < NavigationTree > ) -> Self {
Self {
2021-11-12 16:42:04 +00:00
maybe_navigation_tree : Some ( tree ) ,
2024-04-06 14:36:43 +00:00
.. self . clone ( )
}
2021-11-12 16:42:04 +00:00
}
2022-05-20 20:40:55 +00:00
pub fn text ( & self ) -> Arc < str > {
2024-04-06 14:36:43 +00:00
self . text . clone ( )
2021-11-12 16:42:04 +00:00
}
pub fn line_index ( & self ) -> Arc < LineIndex > {
2024-04-06 14:36:43 +00:00
self . line_index . clone ( )
2021-11-12 16:42:04 +00:00
}
pub fn maybe_navigation_tree ( & self ) -> Option < Arc < NavigationTree > > {
2024-04-06 14:36:43 +00:00
self . maybe_navigation_tree . clone ( )
2021-02-12 11:49:42 +00:00
}
}
2022-04-25 15:23:24 +00:00
type AssetsMap = HashMap < ModuleSpecifier , AssetDocument > ;
2022-01-18 21:28:47 +00:00
fn new_assets_map ( ) -> Arc < Mutex < AssetsMap > > {
2023-01-14 14:36:19 +00:00
let assets = tsc ::LAZILY_LOADED_STATIC_ASSETS
2022-01-18 21:28:47 +00:00
. iter ( )
. map ( | ( k , v ) | {
2023-01-27 15:43:16 +00:00
let url_str = format! ( " asset:/// {k} " ) ;
2022-01-18 21:28:47 +00:00
let specifier = resolve_url ( & url_str ) . unwrap ( ) ;
2022-03-02 21:06:38 +00:00
let asset = AssetDocument ::new ( specifier . clone ( ) , v ) ;
2022-04-25 15:23:24 +00:00
( specifier , asset )
2022-01-18 21:28:47 +00:00
} )
2023-01-24 14:05:54 +00:00
. collect ::< AssetsMap > ( ) ;
2022-01-18 21:28:47 +00:00
Arc ::new ( Mutex ::new ( assets ) )
}
/// Snapshot of Assets.
2021-02-12 11:49:42 +00:00
#[ derive(Debug, Clone) ]
2022-01-18 21:28:47 +00:00
pub struct AssetsSnapshot ( Arc < Mutex < AssetsMap > > ) ;
2021-02-12 11:49:42 +00:00
2022-01-18 21:28:47 +00:00
impl Default for AssetsSnapshot {
2021-02-12 11:49:42 +00:00
fn default ( ) -> Self {
2022-01-18 21:28:47 +00:00
Self ( new_assets_map ( ) )
2021-02-12 11:49:42 +00:00
}
}
2022-01-18 21:28:47 +00:00
impl AssetsSnapshot {
2021-02-12 11:49:42 +00:00
pub fn contains_key ( & self , k : & ModuleSpecifier ) -> bool {
2022-01-18 21:28:47 +00:00
self . 0. lock ( ) . contains_key ( k )
}
2022-04-25 15:23:24 +00:00
pub fn get ( & self , k : & ModuleSpecifier ) -> Option < AssetDocument > {
2022-01-18 21:28:47 +00:00
self . 0. lock ( ) . get ( k ) . cloned ( )
}
}
/// Assets are never updated and so we can safely use this struct across
/// multiple threads without needing to worry about race conditions.
#[ derive(Debug, Clone) ]
pub struct Assets {
ts_server : Arc < TsServer > ,
assets : Arc < Mutex < AssetsMap > > ,
}
impl Assets {
pub fn new ( ts_server : Arc < TsServer > ) -> Self {
Self {
ts_server ,
assets : new_assets_map ( ) ,
}
2021-02-12 11:49:42 +00:00
}
2022-04-25 15:23:24 +00:00
/// Initializes with the assets in the isolate.
2023-06-26 13:10:27 +00:00
pub async fn initialize ( & self , state_snapshot : Arc < StateSnapshot > ) {
2022-04-25 15:23:24 +00:00
let assets = get_isolate_assets ( & self . ts_server , state_snapshot ) . await ;
let mut assets_map = self . assets . lock ( ) ;
for asset in assets {
if ! assets_map . contains_key ( asset . specifier ( ) ) {
assets_map . insert ( asset . specifier ( ) . clone ( ) , asset ) ;
}
}
}
2022-01-18 21:28:47 +00:00
pub fn snapshot ( & self ) -> AssetsSnapshot {
// it's ok to not make a complete copy for snapshotting purposes
// because assets are static
AssetsSnapshot ( self . assets . clone ( ) )
2021-02-12 11:49:42 +00:00
}
2022-04-25 15:23:24 +00:00
pub fn get ( & self , specifier : & ModuleSpecifier ) -> Option < AssetDocument > {
self . assets . lock ( ) . get ( specifier ) . cloned ( )
2021-02-12 11:49:42 +00:00
}
2021-06-04 21:31:44 +00:00
2021-11-18 18:50:24 +00:00
pub fn cache_navigation_tree (
2022-01-18 21:28:47 +00:00
& self ,
2021-06-04 21:31:44 +00:00
specifier : & ModuleSpecifier ,
2021-10-28 23:56:01 +00:00
navigation_tree : Arc < NavigationTree > ,
2021-06-04 21:31:44 +00:00
) -> Result < ( ) , AnyError > {
2022-01-18 21:28:47 +00:00
let mut assets = self . assets . lock ( ) ;
2022-04-25 15:23:24 +00:00
let doc = assets
2021-06-04 21:31:44 +00:00
. get_mut ( specifier )
. ok_or_else ( | | anyhow! ( " Missing asset. " ) ) ? ;
2021-11-12 16:42:04 +00:00
* doc = doc . with_navigation_tree ( navigation_tree ) ;
2021-06-04 21:31:44 +00:00
Ok ( ( ) )
}
2021-02-12 11:49:42 +00:00
}
2022-04-25 15:23:24 +00:00
/// Get all the assets stored in the tsc isolate.
async fn get_isolate_assets (
2020-12-21 13:44:26 +00:00
ts_server : & TsServer ,
2021-11-18 18:50:24 +00:00
state_snapshot : Arc < StateSnapshot > ,
2022-04-25 15:23:24 +00:00
) -> Vec < AssetDocument > {
2024-04-23 15:50:30 +00:00
let req = TscRequest ::GetAssets ;
2023-10-02 06:32:05 +00:00
let res : Value = ts_server . request ( state_snapshot , req ) . await . unwrap ( ) ;
2022-04-25 15:23:24 +00:00
let response_assets = match res {
Value ::Array ( value ) = > value ,
_ = > unreachable! ( ) ,
} ;
let mut assets = Vec ::with_capacity ( response_assets . len ( ) ) ;
for asset in response_assets {
let mut obj = match asset {
Value ::Object ( obj ) = > obj ,
_ = > unreachable! ( ) ,
} ;
let specifier_str = obj . get ( " specifier " ) . unwrap ( ) . as_str ( ) . unwrap ( ) ;
let specifier = ModuleSpecifier ::parse ( specifier_str ) . unwrap ( ) ;
let text = match obj . remove ( " text " ) . unwrap ( ) {
Value ::String ( text ) = > text ,
_ = > unreachable! ( ) ,
} ;
assets . push ( AssetDocument ::new ( specifier , text ) ) ;
2020-12-07 10:46:39 +00:00
}
2022-04-25 15:23:24 +00:00
assets
2020-12-07 10:46:39 +00:00
}
2022-02-09 23:08:53 +00:00
fn get_tag_body_text (
tag : & JsDocTagInfo ,
language_server : & language_server ::Inner ,
) -> Option < String > {
2021-05-27 23:33:11 +00:00
tag . text . as_ref ( ) . map ( | display_parts | {
// TODO(@kitsonk) check logic in vscode about handling this API change in
// tsserver
2022-02-09 23:08:53 +00:00
let text = display_parts_to_string ( display_parts , language_server ) ;
2021-05-27 23:33:11 +00:00
match tag . name . as_str ( ) {
" example " = > {
2021-09-16 02:07:52 +00:00
if CAPTION_RE . is_match ( & text ) {
CAPTION_RE
2021-05-27 23:33:11 +00:00
. replace ( & text , | c : & Captures | {
format! ( " {} \n \n {} " , & c [ 1 ] , make_codeblock ( & c [ 2 ] ) )
} )
. to_string ( )
} else {
make_codeblock ( & text )
}
}
2021-09-16 02:07:52 +00:00
" author " = > EMAIL_MATCH_RE
. replace ( & text , | c : & Captures | format! ( " {} {} " , & c [ 1 ] , & c [ 2 ] ) )
. to_string ( ) ,
2021-05-27 23:33:11 +00:00
" default " = > make_codeblock ( & text ) ,
_ = > replace_links ( & text ) ,
2020-12-07 10:46:39 +00:00
}
} )
}
2022-02-09 23:08:53 +00:00
fn get_tag_documentation (
tag : & JsDocTagInfo ,
language_server : & language_server ::Inner ,
) -> String {
2020-12-07 10:46:39 +00:00
match tag . name . as_str ( ) {
" augments " | " extends " | " param " | " template " = > {
2021-05-27 23:33:11 +00:00
if let Some ( display_parts ) = & tag . text {
// TODO(@kitsonk) check logic in vscode about handling this API change
// in tsserver
2022-02-09 23:08:53 +00:00
let text = display_parts_to_string ( display_parts , language_server ) ;
2021-09-16 02:07:52 +00:00
let body : Vec < & str > = PART_RE . split ( & text ) . collect ( ) ;
2020-12-07 10:46:39 +00:00
if body . len ( ) = = 3 {
let param = body [ 1 ] ;
let doc = body [ 2 ] ;
let label = format! ( " *@ {} * ` {} ` " , tag . name , param ) ;
if doc . is_empty ( ) {
return label ;
}
if doc . contains ( '\n' ) {
return format! ( " {} \n {} " , label , replace_links ( doc ) ) ;
} else {
return format! ( " {} - {} " , label , replace_links ( doc ) ) ;
}
}
}
}
_ = > ( ) ,
}
let label = format! ( " *@ {} * " , tag . name ) ;
2022-02-09 23:08:53 +00:00
let maybe_text = get_tag_body_text ( tag , language_server ) ;
2020-12-07 10:46:39 +00:00
if let Some ( text ) = maybe_text {
if text . contains ( '\n' ) {
2023-01-27 15:43:16 +00:00
format! ( " {label} \n {text} " )
2020-12-07 10:46:39 +00:00
} else {
2023-01-27 15:43:16 +00:00
format! ( " {label} - {text} " )
2020-12-07 10:46:39 +00:00
}
} else {
label
}
}
fn make_codeblock ( text : & str ) -> String {
2021-09-16 02:07:52 +00:00
if CODEBLOCK_RE . is_match ( text ) {
2020-12-07 10:46:39 +00:00
text . to_string ( )
} else {
2023-01-27 15:43:16 +00:00
format! ( " ``` \n {text} \n ``` " )
2020-12-07 10:46:39 +00:00
}
}
/// Replace JSDoc like links (`{@link http://example.com}`) with markdown links
2022-02-09 23:08:53 +00:00
fn replace_links < S : AsRef < str > > ( text : S ) -> String {
2021-09-16 02:07:52 +00:00
JSDOC_LINKS_RE
2022-02-09 23:08:53 +00:00
. replace_all ( text . as_ref ( ) , | c : & Captures | match & c [ 1 ] {
2020-12-07 10:46:39 +00:00
" linkcode " = > format! (
" [`{}`]({}) " ,
if c . get ( 3 ) . is_none ( ) {
& c [ 2 ]
} else {
c [ 3 ] . trim ( )
} ,
& c [ 2 ]
) ,
_ = > format! (
" [{}]({}) " ,
if c . get ( 3 ) . is_none ( ) {
& c [ 2 ]
} else {
c [ 3 ] . trim ( )
} ,
& c [ 2 ]
) ,
} )
. to_string ( )
}
2021-03-15 22:01:41 +00:00
fn parse_kind_modifier ( kind_modifiers : & str ) -> HashSet < & str > {
2021-09-16 02:07:52 +00:00
PART_KIND_MODIFIER_RE . split ( kind_modifiers ) . collect ( )
2021-03-15 22:01:41 +00:00
}
2021-04-19 05:11:26 +00:00
#[ derive(Debug, Deserialize) ]
#[ serde(untagged) ]
pub enum OneOrMany < T > {
One ( T ) ,
Many ( Vec < T > ) ,
}
2021-03-15 22:01:41 +00:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq) ]
2020-12-07 10:46:39 +00:00
pub enum ScriptElementKind {
#[ serde(rename = " " ) ]
Unknown ,
#[ serde(rename = " warning " ) ]
Warning ,
#[ serde(rename = " keyword " ) ]
Keyword ,
#[ serde(rename = " script " ) ]
ScriptElement ,
#[ serde(rename = " module " ) ]
ModuleElement ,
#[ serde(rename = " class " ) ]
ClassElement ,
#[ serde(rename = " local class " ) ]
LocalClassElement ,
#[ serde(rename = " interface " ) ]
InterfaceElement ,
#[ serde(rename = " type " ) ]
TypeElement ,
#[ serde(rename = " enum " ) ]
EnumElement ,
#[ serde(rename = " enum member " ) ]
EnumMemberElement ,
#[ serde(rename = " var " ) ]
VariableElement ,
#[ serde(rename = " local var " ) ]
LocalVariableElement ,
#[ serde(rename = " function " ) ]
FunctionElement ,
#[ serde(rename = " local function " ) ]
LocalFunctionElement ,
#[ serde(rename = " method " ) ]
MemberFunctionElement ,
#[ serde(rename = " getter " ) ]
MemberGetAccessorElement ,
#[ serde(rename = " setter " ) ]
MemberSetAccessorElement ,
#[ serde(rename = " property " ) ]
MemberVariableElement ,
#[ serde(rename = " constructor " ) ]
ConstructorImplementationElement ,
#[ serde(rename = " call " ) ]
CallSignatureElement ,
#[ serde(rename = " index " ) ]
IndexSignatureElement ,
#[ serde(rename = " construct " ) ]
ConstructSignatureElement ,
#[ serde(rename = " parameter " ) ]
ParameterElement ,
#[ serde(rename = " type parameter " ) ]
TypeParameterElement ,
#[ serde(rename = " primitive type " ) ]
PrimitiveType ,
#[ serde(rename = " label " ) ]
Label ,
#[ serde(rename = " alias " ) ]
Alias ,
#[ serde(rename = " const " ) ]
ConstElement ,
#[ serde(rename = " let " ) ]
LetElement ,
#[ serde(rename = " directory " ) ]
Directory ,
#[ serde(rename = " external module name " ) ]
ExternalModuleName ,
#[ serde(rename = " JSX attribute " ) ]
JsxAttribute ,
#[ serde(rename = " string " ) ]
String ,
2021-11-09 10:45:40 +00:00
#[ serde(rename = " link " ) ]
Link ,
#[ serde(rename = " link name " ) ]
LinkName ,
2022-07-11 23:35:18 +00:00
#[ serde(rename = " link text " ) ]
2021-11-09 10:45:40 +00:00
LinkText ,
2020-12-07 10:46:39 +00:00
}
2021-03-15 22:01:41 +00:00
impl Default for ScriptElementKind {
fn default ( ) -> Self {
Self ::Unknown
}
}
2021-11-09 10:45:40 +00:00
/// This mirrors the method `convertKind` in `completions.ts` in vscode
2021-01-29 19:34:33 +00:00
impl From < ScriptElementKind > for lsp ::CompletionItemKind {
2020-12-08 10:36:13 +00:00
fn from ( kind : ScriptElementKind ) -> Self {
match kind {
ScriptElementKind ::PrimitiveType | ScriptElementKind ::Keyword = > {
2021-11-25 01:10:12 +00:00
lsp ::CompletionItemKind ::KEYWORD
2020-12-08 10:36:13 +00:00
}
2021-03-15 22:01:41 +00:00
ScriptElementKind ::ConstElement
| ScriptElementKind ::LetElement
2020-12-08 10:36:13 +00:00
| ScriptElementKind ::VariableElement
| ScriptElementKind ::LocalVariableElement
2021-03-15 22:01:41 +00:00
| ScriptElementKind ::Alias
| ScriptElementKind ::ParameterElement = > {
2021-11-25 01:10:12 +00:00
lsp ::CompletionItemKind ::VARIABLE
2021-03-15 22:01:41 +00:00
}
2020-12-08 10:36:13 +00:00
ScriptElementKind ::MemberVariableElement
| ScriptElementKind ::MemberGetAccessorElement
| ScriptElementKind ::MemberSetAccessorElement = > {
2021-11-25 01:10:12 +00:00
lsp ::CompletionItemKind ::FIELD
2021-03-15 22:01:41 +00:00
}
ScriptElementKind ::FunctionElement
| ScriptElementKind ::LocalFunctionElement = > {
2021-11-25 01:10:12 +00:00
lsp ::CompletionItemKind ::FUNCTION
2020-12-08 10:36:13 +00:00
}
ScriptElementKind ::MemberFunctionElement
| ScriptElementKind ::ConstructSignatureElement
| ScriptElementKind ::CallSignatureElement
2021-03-15 22:01:41 +00:00
| ScriptElementKind ::IndexSignatureElement = > {
2021-11-25 01:10:12 +00:00
lsp ::CompletionItemKind ::METHOD
2021-03-15 22:01:41 +00:00
}
2021-11-25 01:10:12 +00:00
ScriptElementKind ::EnumElement = > lsp ::CompletionItemKind ::ENUM ,
2021-03-15 22:01:41 +00:00
ScriptElementKind ::EnumMemberElement = > {
2021-11-25 01:10:12 +00:00
lsp ::CompletionItemKind ::ENUM_MEMBER
2021-03-15 22:01:41 +00:00
}
2020-12-08 10:36:13 +00:00
ScriptElementKind ::ModuleElement
2021-03-15 22:01:41 +00:00
| ScriptElementKind ::ExternalModuleName = > {
2021-11-25 01:10:12 +00:00
lsp ::CompletionItemKind ::MODULE
2020-12-08 10:36:13 +00:00
}
2021-03-15 22:01:41 +00:00
ScriptElementKind ::ClassElement | ScriptElementKind ::TypeElement = > {
2021-11-25 01:10:12 +00:00
lsp ::CompletionItemKind ::CLASS
2020-12-08 10:36:13 +00:00
}
2021-11-25 01:10:12 +00:00
ScriptElementKind ::InterfaceElement = > lsp ::CompletionItemKind ::INTERFACE ,
ScriptElementKind ::Warning = > lsp ::CompletionItemKind ::TEXT ,
ScriptElementKind ::ScriptElement = > lsp ::CompletionItemKind ::FILE ,
ScriptElementKind ::Directory = > lsp ::CompletionItemKind ::FOLDER ,
ScriptElementKind ::String = > lsp ::CompletionItemKind ::CONSTANT ,
_ = > lsp ::CompletionItemKind ::PROPERTY ,
2020-12-08 10:36:13 +00:00
}
}
}
2021-11-09 10:45:40 +00:00
/// This mirrors `fromProtocolScriptElementKind` in vscode
2021-04-19 05:11:26 +00:00
impl From < ScriptElementKind > for lsp ::SymbolKind {
fn from ( kind : ScriptElementKind ) -> Self {
match kind {
2021-11-25 01:10:12 +00:00
ScriptElementKind ::ModuleElement = > Self ::MODULE ,
2021-11-23 00:08:56 +00:00
// this is only present in `getSymbolKind` in `workspaceSymbols` in
// vscode, but seems strange it isn't consistent.
2021-11-25 01:10:12 +00:00
ScriptElementKind ::TypeElement = > Self ::CLASS ,
ScriptElementKind ::ClassElement = > Self ::CLASS ,
ScriptElementKind ::EnumElement = > Self ::ENUM ,
ScriptElementKind ::EnumMemberElement = > Self ::ENUM_MEMBER ,
ScriptElementKind ::InterfaceElement = > Self ::INTERFACE ,
ScriptElementKind ::IndexSignatureElement = > Self ::METHOD ,
ScriptElementKind ::CallSignatureElement = > Self ::METHOD ,
ScriptElementKind ::MemberFunctionElement = > Self ::METHOD ,
2021-11-23 00:08:56 +00:00
// workspaceSymbols in vscode treats them as fields, which does seem more
// semantically correct while `fromProtocolScriptElementKind` treats them
// as properties.
2021-11-25 01:10:12 +00:00
ScriptElementKind ::MemberVariableElement = > Self ::FIELD ,
ScriptElementKind ::MemberGetAccessorElement = > Self ::FIELD ,
ScriptElementKind ::MemberSetAccessorElement = > Self ::FIELD ,
ScriptElementKind ::VariableElement = > Self ::VARIABLE ,
ScriptElementKind ::LetElement = > Self ::VARIABLE ,
ScriptElementKind ::ConstElement = > Self ::VARIABLE ,
ScriptElementKind ::LocalVariableElement = > Self ::VARIABLE ,
ScriptElementKind ::Alias = > Self ::VARIABLE ,
ScriptElementKind ::FunctionElement = > Self ::FUNCTION ,
ScriptElementKind ::LocalFunctionElement = > Self ::FUNCTION ,
ScriptElementKind ::ConstructSignatureElement = > Self ::CONSTRUCTOR ,
ScriptElementKind ::ConstructorImplementationElement = > Self ::CONSTRUCTOR ,
ScriptElementKind ::TypeParameterElement = > Self ::TYPE_PARAMETER ,
ScriptElementKind ::String = > Self ::STRING ,
_ = > Self ::VARIABLE ,
2021-04-19 05:11:26 +00:00
}
}
}
2021-02-04 18:53:02 +00:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq) ]
2020-12-07 10:46:39 +00:00
#[ serde(rename_all = " camelCase " ) ]
pub struct TextSpan {
2021-02-01 03:30:41 +00:00
pub start : u32 ,
pub length : u32 ,
2020-12-07 10:46:39 +00:00
}
impl TextSpan {
2022-10-16 02:39:43 +00:00
pub fn from_range (
range : & lsp ::Range ,
line_index : Arc < LineIndex > ,
) -> Result < Self , AnyError > {
let start = line_index . offset_tsc ( range . start ) ? ;
let length = line_index . offset_tsc ( range . end ) ? - start ;
Ok ( Self { start , length } )
}
2021-10-28 23:56:01 +00:00
pub fn to_range ( & self , line_index : Arc < LineIndex > ) -> lsp ::Range {
2021-01-29 19:34:33 +00:00
lsp ::Range {
2021-01-22 10:03:16 +00:00
start : line_index . position_tsc ( self . start . into ( ) ) ,
end : line_index . position_tsc ( TextSize ::from ( self . start + self . length ) ) ,
2020-12-07 10:46:39 +00:00
}
}
}
2022-06-01 00:19:18 +00:00
#[ derive(Debug, Serialize, Deserialize, Clone) ]
2020-12-07 10:46:39 +00:00
#[ serde(rename_all = " camelCase " ) ]
pub struct SymbolDisplayPart {
text : String ,
kind : String ,
2022-02-09 23:08:53 +00:00
// This is only on `JSDocLinkDisplayPart` which extends `SymbolDisplayPart`
// but is only used as an upcast of a `SymbolDisplayPart` and not explicitly
// returned by any API, so it is safe to add it as an optional value.
2023-10-02 06:32:05 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2022-02-09 23:08:53 +00:00
target : Option < DocumentSpan > ,
2020-12-07 10:46:39 +00:00
}
2022-07-11 23:35:18 +00:00
#[ derive(Debug, Deserialize, Serialize) ]
2020-12-07 10:46:39 +00:00
#[ serde(rename_all = " camelCase " ) ]
2021-03-25 18:17:37 +00:00
pub struct JsDocTagInfo {
2020-12-07 10:46:39 +00:00
name : String ,
2021-05-27 23:33:11 +00:00
text : Option < Vec < SymbolDisplayPart > > ,
2020-12-07 10:46:39 +00:00
}
2022-02-23 21:01:20 +00:00
// Note: the tsc protocol contains fields that are part of the protocol but
// not currently used. They are commented out in the structures so it is clear
// that they exist.
2020-12-07 10:46:39 +00:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct QuickInfo {
2022-02-23 21:01:20 +00:00
// kind: ScriptElementKind,
// kind_modifiers: String,
2020-12-07 10:46:39 +00:00
text_span : TextSpan ,
display_parts : Option < Vec < SymbolDisplayPart > > ,
documentation : Option < Vec < SymbolDisplayPart > > ,
2021-03-25 18:17:37 +00:00
tags : Option < Vec < JsDocTagInfo > > ,
2020-12-07 10:46:39 +00:00
}
2022-02-09 23:08:53 +00:00
#[ derive(Default) ]
struct Link {
name : Option < String > ,
target : Option < DocumentSpan > ,
text : Option < String > ,
linkcode : bool ,
}
/// Takes `SymbolDisplayPart` items and converts them into a string, handling
/// any `{@link Symbol}` and `{@linkcode Symbol}` JSDoc tags and linking them
/// to the their source location.
fn display_parts_to_string (
parts : & [ SymbolDisplayPart ] ,
language_server : & language_server ::Inner ,
) -> String {
let mut out = Vec ::< String > ::new ( ) ;
let mut current_link : Option < Link > = None ;
for part in parts {
match part . kind . as_str ( ) {
" link " = > {
if let Some ( link ) = current_link . as_mut ( ) {
if let Some ( target ) = & link . target {
if let Some ( specifier ) = target . to_target ( language_server ) {
let link_text = link . text . clone ( ) . unwrap_or_else ( | | {
link
. name
. clone ( )
. map ( | ref n | n . replace ( '`' , " \\ ` " ) )
. unwrap_or_else ( | | " " . to_string ( ) )
} ) ;
let link_str = if link . linkcode {
2023-01-27 15:43:16 +00:00
format! ( " [` {link_text} `]( {specifier} ) " )
2022-02-09 23:08:53 +00:00
} else {
2023-01-27 15:43:16 +00:00
format! ( " [ {link_text} ]( {specifier} ) " )
2022-02-09 23:08:53 +00:00
} ;
out . push ( link_str ) ;
}
} else {
let maybe_text = link . text . clone ( ) . or_else ( | | link . name . clone ( ) ) ;
if let Some ( text ) = maybe_text {
if HTTP_RE . is_match ( & text ) {
let parts : Vec < & str > = text . split ( ' ' ) . collect ( ) ;
if parts . len ( ) = = 1 {
out . push ( parts [ 0 ] . to_string ( ) ) ;
} else {
let link_text = parts [ 1 .. ] . join ( " " ) . replace ( '`' , " \\ ` " ) ;
let link_str = if link . linkcode {
format! ( " [` {} `]( {} ) " , link_text , parts [ 0 ] )
} else {
format! ( " [ {} ]( {} ) " , link_text , parts [ 0 ] )
} ;
out . push ( link_str ) ;
}
} else {
out . push ( text . replace ( '`' , " \\ ` " ) ) ;
}
}
}
current_link = None ;
} else {
current_link = Some ( Link {
linkcode : part . text . as_str ( ) = = " {@linkcode " ,
.. Default ::default ( )
} ) ;
}
}
" linkName " = > {
if let Some ( link ) = current_link . as_mut ( ) {
link . name = Some ( part . text . clone ( ) ) ;
link . target = part . target . clone ( ) ;
}
}
" linkText " = > {
if let Some ( link ) = current_link . as_mut ( ) {
link . name = Some ( part . text . clone ( ) ) ;
}
}
2024-03-27 15:58:18 +00:00
_ = > out . push (
// should decode percent-encoding string when hovering over the right edge of module specifier like below
// module "file:///path/to/🦕"
to_percent_decoded_str ( & part . text ) ,
// NOTE: The reason why an example above that lacks `.ts` extension is caused by the implementation of tsc itself.
// The request `tsc.request.getQuickInfoAtPosition` receives the payload from tsc host as follows.
// {
// text_span: {
// start: 19,
// length: 9,
// },
// displayParts:
// [
// {
// text: "module",
// kind: "keyword",
// target: null,
// },
// {
// text: " ",
// kind: "space",
// target: null,
// },
// {
// text: "\"file:///path/to/%F0%9F%A6%95\"",
// kind: "stringLiteral",
// target: null,
// },
// ],
// documentation: [],
// tags: null,
// }
//
// related issue: https://github.com/denoland/deno/issues/16058
) ,
2022-02-09 23:08:53 +00:00
}
}
replace_links ( out . join ( " " ) )
}
2020-12-07 10:46:39 +00:00
impl QuickInfo {
2022-03-23 13:54:22 +00:00
pub fn to_hover (
2022-02-09 23:08:53 +00:00
& self ,
line_index : Arc < LineIndex > ,
language_server : & language_server ::Inner ,
) -> lsp ::Hover {
let mut parts = Vec ::< lsp ::MarkedString > ::new ( ) ;
2021-03-15 22:01:41 +00:00
if let Some ( display_string ) = self
. display_parts
. clone ( )
2022-02-09 23:08:53 +00:00
. map ( | p | display_parts_to_string ( & p , language_server ) )
2020-12-07 10:46:39 +00:00
{
2022-02-09 23:08:53 +00:00
parts . push ( lsp ::MarkedString ::from_language_code (
2020-12-07 10:46:39 +00:00
" typescript " . to_string ( ) ,
display_string ,
) ) ;
}
2021-03-15 22:01:41 +00:00
if let Some ( documentation ) = self
. documentation
. clone ( )
2022-02-09 23:08:53 +00:00
. map ( | p | display_parts_to_string ( & p , language_server ) )
2020-12-07 10:46:39 +00:00
{
2022-02-09 23:08:53 +00:00
parts . push ( lsp ::MarkedString ::from_markdown ( documentation ) ) ;
2020-12-07 10:46:39 +00:00
}
if let Some ( tags ) = & self . tags {
let tags_preview = tags
. iter ( )
2022-02-09 23:08:53 +00:00
. map ( | tag_info | get_tag_documentation ( tag_info , language_server ) )
2020-12-07 10:46:39 +00:00
. collect ::< Vec < String > > ( )
. join ( " \n \n " ) ;
if ! tags_preview . is_empty ( ) {
2022-02-09 23:08:53 +00:00
parts . push ( lsp ::MarkedString ::from_markdown ( format! (
2023-01-27 15:43:16 +00:00
" \n \n {tags_preview} "
2020-12-07 10:46:39 +00:00
) ) ) ;
}
}
2021-01-29 19:34:33 +00:00
lsp ::Hover {
2022-02-09 23:08:53 +00:00
contents : lsp ::HoverContents ::Array ( parts ) ,
2020-12-07 10:46:39 +00:00
range : Some ( self . text_span . to_range ( line_index ) ) ,
}
}
}
2022-06-01 00:19:18 +00:00
#[ derive(Debug, Clone, Deserialize, Serialize) ]
2021-01-12 21:53:27 +00:00
#[ serde(rename_all = " camelCase " ) ]
pub struct DocumentSpan {
text_span : TextSpan ,
pub file_name : String ,
original_text_span : Option < TextSpan > ,
2022-02-23 21:01:20 +00:00
// original_file_name: Option<String>,
2021-01-12 21:53:27 +00:00
context_span : Option < TextSpan > ,
original_context_span : Option < TextSpan > ,
}
2023-09-29 19:44:59 +00:00
impl DocumentSpan {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . file_name = specifier_map . normalize ( & self . file_name ) ? . to_string ( ) ;
Ok ( ( ) )
}
}
2021-01-12 21:53:27 +00:00
impl DocumentSpan {
2022-04-25 15:23:24 +00:00
pub fn to_link (
2021-01-12 21:53:27 +00:00
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2022-01-19 16:38:40 +00:00
language_server : & language_server ::Inner ,
2021-02-06 12:39:01 +00:00
) -> Option < lsp ::LocationLink > {
2023-10-02 06:32:05 +00:00
let target_specifier = resolve_url ( & self . file_name ) . ok ( ) ? ;
2022-04-25 15:23:24 +00:00
let target_asset_or_doc =
language_server . get_maybe_asset_or_document ( & target_specifier ) ? ;
2021-11-12 16:42:04 +00:00
let target_line_index = target_asset_or_doc . line_index ( ) ;
2021-02-18 04:37:05 +00:00
let target_uri = language_server
. url_map
. normalize_specifier ( & target_specifier )
2021-07-25 05:33:42 +00:00
. ok ( ) ? ;
2021-02-18 04:37:05 +00:00
let ( target_range , target_selection_range ) =
if let Some ( context_span ) = & self . context_span {
(
2021-10-28 23:56:01 +00:00
context_span . to_range ( target_line_index . clone ( ) ) ,
self . text_span . to_range ( target_line_index ) ,
2021-02-18 04:37:05 +00:00
)
} else {
(
2021-10-28 23:56:01 +00:00
self . text_span . to_range ( target_line_index . clone ( ) ) ,
self . text_span . to_range ( target_line_index ) ,
2021-02-18 04:37:05 +00:00
)
2021-01-12 21:53:27 +00:00
} ;
2021-02-18 04:37:05 +00:00
let origin_selection_range =
if let Some ( original_context_span ) = & self . original_context_span {
Some ( original_context_span . to_range ( line_index ) )
} else {
2021-03-25 18:17:37 +00:00
self
. original_text_span
. as_ref ( )
. map ( | original_text_span | original_text_span . to_range ( line_index ) )
2021-02-18 04:37:05 +00:00
} ;
let link = lsp ::LocationLink {
origin_selection_range ,
2023-03-23 14:23:04 +00:00
target_uri : target_uri . into_url ( ) ,
2021-02-18 04:37:05 +00:00
target_range ,
target_selection_range ,
} ;
Some ( link )
2021-01-12 21:53:27 +00:00
}
2022-02-09 23:08:53 +00:00
/// Convert the `DocumentSpan` into a specifier that can be sent to the client
/// to link to the target document span. Used for converting JSDoc symbol
/// links to markdown links.
fn to_target (
& self ,
language_server : & language_server ::Inner ,
) -> Option < ModuleSpecifier > {
2023-10-02 06:32:05 +00:00
let specifier = resolve_url ( & self . file_name ) . ok ( ) ? ;
2022-02-09 23:08:53 +00:00
let asset_or_doc =
2022-04-25 15:23:24 +00:00
language_server . get_maybe_asset_or_document ( & specifier ) ? ;
2022-02-09 23:08:53 +00:00
let line_index = asset_or_doc . line_index ( ) ;
let range = self . text_span . to_range ( line_index ) ;
let mut target = language_server
. url_map
. normalize_specifier ( & specifier )
2023-03-23 14:23:04 +00:00
. ok ( ) ?
. into_url ( ) ;
2022-02-09 23:08:53 +00:00
target . set_fragment ( Some ( & format! (
" L{},{} " ,
range . start . line + 1 ,
range . start . character + 1
) ) ) ;
Some ( target )
}
2021-01-12 21:53:27 +00:00
}
2021-11-23 00:08:56 +00:00
#[ derive(Debug, Clone, Deserialize) ]
pub enum MatchKind {
#[ serde(rename = " exact " ) ]
Exact ,
#[ serde(rename = " prefix " ) ]
Prefix ,
#[ serde(rename = " substring " ) ]
Substring ,
#[ serde(rename = " camelCase " ) ]
CamelCase ,
}
#[ derive(Debug, Clone, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct NavigateToItem {
name : String ,
kind : ScriptElementKind ,
kind_modifiers : String ,
2022-02-23 21:01:20 +00:00
// match_kind: MatchKind,
// is_case_sensitive: bool,
2021-11-23 00:08:56 +00:00
file_name : String ,
text_span : TextSpan ,
container_name : Option < String > ,
2022-02-23 21:01:20 +00:00
// container_kind: ScriptElementKind,
2021-11-23 00:08:56 +00:00
}
2023-09-29 19:44:59 +00:00
impl NavigateToItem {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . file_name = specifier_map . normalize ( & self . file_name ) ? . to_string ( ) ;
Ok ( ( ) )
}
}
2021-11-23 00:08:56 +00:00
impl NavigateToItem {
2022-04-25 15:23:24 +00:00
pub fn to_symbol_information (
2021-11-23 00:08:56 +00:00
& self ,
2022-12-20 01:22:17 +00:00
language_server : & language_server ::Inner ,
2021-11-23 00:08:56 +00:00
) -> Option < lsp ::SymbolInformation > {
2023-10-02 06:32:05 +00:00
let specifier = resolve_url ( & self . file_name ) . ok ( ) ? ;
2022-04-25 15:23:24 +00:00
let asset_or_doc =
language_server . get_asset_or_document ( & specifier ) . ok ( ) ? ;
2021-11-23 00:08:56 +00:00
let line_index = asset_or_doc . line_index ( ) ;
let uri = language_server
. url_map
. normalize_specifier ( & specifier )
. ok ( ) ? ;
let range = self . text_span . to_range ( line_index ) ;
2023-03-23 14:23:04 +00:00
let location = lsp ::Location {
uri : uri . into_url ( ) ,
range ,
} ;
2021-11-23 00:08:56 +00:00
let mut tags : Option < Vec < lsp ::SymbolTag > > = None ;
let kind_modifiers = parse_kind_modifier ( & self . kind_modifiers ) ;
if kind_modifiers . contains ( " deprecated " ) {
2021-11-25 01:10:12 +00:00
tags = Some ( vec! [ lsp ::SymbolTag ::DEPRECATED ] ) ;
2021-11-23 00:08:56 +00:00
}
// The field `deprecated` is deprecated but SymbolInformation does not have
// a default, therefore we have to supply the deprecated deprecated
// field. It is like a bad version of Inception.
#[ allow(deprecated) ]
Some ( lsp ::SymbolInformation {
name : self . name . clone ( ) ,
kind : self . kind . clone ( ) . into ( ) ,
tags ,
deprecated : None ,
location ,
container_name : self . container_name . clone ( ) ,
} )
}
}
2022-10-16 02:39:43 +00:00
#[ derive(Debug, Clone, Deserialize) ]
pub enum InlayHintKind {
Type ,
Parameter ,
Enum ,
}
impl InlayHintKind {
pub fn to_lsp ( & self ) -> Option < lsp ::InlayHintKind > {
match self {
Self ::Enum = > None ,
Self ::Parameter = > Some ( lsp ::InlayHintKind ::PARAMETER ) ,
Self ::Type = > Some ( lsp ::InlayHintKind ::TYPE ) ,
}
}
}
#[ derive(Debug, Clone, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct InlayHint {
pub text : String ,
pub position : u32 ,
pub kind : InlayHintKind ,
pub whitespace_before : Option < bool > ,
pub whitespace_after : Option < bool > ,
}
impl InlayHint {
pub fn to_lsp ( & self , line_index : Arc < LineIndex > ) -> lsp ::InlayHint {
lsp ::InlayHint {
position : line_index . position_tsc ( self . position . into ( ) ) ,
label : lsp ::InlayHintLabel ::String ( self . text . clone ( ) ) ,
kind : self . kind . to_lsp ( ) ,
padding_left : self . whitespace_before ,
padding_right : self . whitespace_after ,
text_edits : None ,
tooltip : None ,
data : None ,
}
}
}
2021-02-01 03:30:41 +00:00
#[ derive(Debug, Clone, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct NavigationTree {
pub text : String ,
pub kind : ScriptElementKind ,
pub kind_modifiers : String ,
pub spans : Vec < TextSpan > ,
pub name_span : Option < TextSpan > ,
pub child_items : Option < Vec < NavigationTree > > ,
}
impl NavigationTree {
pub fn to_code_lens (
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2021-02-01 03:30:41 +00:00
specifier : & ModuleSpecifier ,
2021-06-04 21:31:44 +00:00
source : & code_lens ::CodeLensSource ,
2021-02-01 03:30:41 +00:00
) -> lsp ::CodeLens {
2021-02-08 10:45:10 +00:00
let range = if let Some ( name_span ) = & self . name_span {
name_span . to_range ( line_index )
} else if ! self . spans . is_empty ( ) {
let span = & self . spans [ 0 ] ;
span . to_range ( line_index )
} else {
lsp ::Range ::default ( )
} ;
2021-02-01 03:30:41 +00:00
lsp ::CodeLens {
2021-02-08 10:45:10 +00:00
range ,
2021-02-01 03:30:41 +00:00
command : None ,
data : Some ( json! ( {
" specifier " : specifier ,
" source " : source
} ) ) ,
}
}
2021-04-20 01:29:27 +00:00
pub fn collect_document_symbols (
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2021-04-20 01:29:27 +00:00
document_symbols : & mut Vec < lsp ::DocumentSymbol > ,
) -> bool {
let mut should_include = self . should_include_entry ( ) ;
if ! should_include
2023-03-15 21:46:36 +00:00
& & self
. child_items
. as_ref ( )
. map ( | v | v . is_empty ( ) )
. unwrap_or ( true )
2021-04-20 01:29:27 +00:00
{
return false ;
}
let children = self
. child_items
2023-03-15 21:46:36 +00:00
. as_deref ( )
. unwrap_or ( & [ ] as & [ NavigationTree ] ) ;
2021-04-20 01:29:27 +00:00
for span in self . spans . iter ( ) {
let range = TextRange ::at ( span . start . into ( ) , span . length . into ( ) ) ;
let mut symbol_children = Vec ::< lsp ::DocumentSymbol > ::new ( ) ;
for child in children . iter ( ) {
let should_traverse_child = child
. spans
. iter ( )
. map ( | child_span | {
TextRange ::at ( child_span . start . into ( ) , child_span . length . into ( ) )
} )
. any ( | child_range | range . intersect ( child_range ) . is_some ( ) ) ;
if should_traverse_child {
2021-10-28 23:56:01 +00:00
let included_child = child
. collect_document_symbols ( line_index . clone ( ) , & mut symbol_children ) ;
2021-04-20 01:29:27 +00:00
should_include = should_include | | included_child ;
}
}
if should_include {
let mut selection_span = span ;
if let Some ( name_span ) = self . name_span . as_ref ( ) {
let name_range =
TextRange ::at ( name_span . start . into ( ) , name_span . length . into ( ) ) ;
if range . contains_range ( name_range ) {
selection_span = name_span ;
}
}
2021-11-23 00:08:56 +00:00
let name = match self . kind {
ScriptElementKind ::MemberGetAccessorElement = > {
format! ( " (get) {} " , self . text )
}
ScriptElementKind ::MemberSetAccessorElement = > {
format! ( " (set) {} " , self . text )
}
_ = > self . text . clone ( ) ,
} ;
2021-04-20 01:29:27 +00:00
let mut tags : Option < Vec < lsp ::SymbolTag > > = None ;
let kind_modifiers = parse_kind_modifier ( & self . kind_modifiers ) ;
if kind_modifiers . contains ( " deprecated " ) {
2021-11-25 01:10:12 +00:00
tags = Some ( vec! [ lsp ::SymbolTag ::DEPRECATED ] ) ;
2021-04-20 01:29:27 +00:00
}
let children = if ! symbol_children . is_empty ( ) {
Some ( symbol_children )
} else {
None
} ;
// The field `deprecated` is deprecated but DocumentSymbol does not have
// a default, therefore we have to supply the deprecated deprecated
// field. It is like a bad version of Inception.
#[ allow(deprecated) ]
document_symbols . push ( lsp ::DocumentSymbol {
2021-11-23 00:08:56 +00:00
name ,
2021-04-20 01:29:27 +00:00
kind : self . kind . clone ( ) . into ( ) ,
2021-10-28 23:56:01 +00:00
range : span . to_range ( line_index . clone ( ) ) ,
selection_range : selection_span . to_range ( line_index . clone ( ) ) ,
2021-04-20 01:29:27 +00:00
tags ,
children ,
detail : None ,
deprecated : None ,
} )
}
}
should_include
}
fn should_include_entry ( & self ) -> bool {
if let ScriptElementKind ::Alias = self . kind {
return false ;
}
! self . text . is_empty ( ) & & self . text ! = " <function> " & & self . text ! = " <class> "
}
2021-02-01 03:30:41 +00:00
pub fn walk < F > ( & self , callback : & F )
where
F : Fn ( & NavigationTree , Option < & NavigationTree > ) ,
{
callback ( self , None ) ;
if let Some ( child_items ) = & self . child_items {
for child in child_items {
child . walk_child ( callback , self ) ;
}
}
}
fn walk_child < F > ( & self , callback : & F , parent : & NavigationTree )
where
F : Fn ( & NavigationTree , Option < & NavigationTree > ) ,
{
callback ( self , Some ( parent ) ) ;
if let Some ( child_items ) = & self . child_items {
for child in child_items {
child . walk_child ( callback , self ) ;
}
}
}
}
2021-01-12 21:53:27 +00:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ImplementationLocation {
#[ serde(flatten) ]
pub document_span : DocumentSpan ,
// ImplementationLocation props
2022-02-23 21:01:20 +00:00
// kind: ScriptElementKind,
// display_parts: Vec<SymbolDisplayPart>,
2021-01-12 21:53:27 +00:00
}
2021-02-08 10:45:10 +00:00
impl ImplementationLocation {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . document_span . normalize ( specifier_map ) ? ;
Ok ( ( ) )
}
2022-03-23 13:54:22 +00:00
pub fn to_location (
2021-02-18 04:37:05 +00:00
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2022-01-19 16:38:40 +00:00
language_server : & language_server ::Inner ,
2021-02-18 04:37:05 +00:00
) -> lsp ::Location {
2023-10-02 06:32:05 +00:00
let specifier = resolve_url ( & self . document_span . file_name )
2023-02-06 21:49:49 +00:00
. unwrap_or_else ( | _ | ModuleSpecifier ::parse ( " deno://invalid " ) . unwrap ( ) ) ;
2021-02-18 04:37:05 +00:00
let uri = language_server
. url_map
. normalize_specifier ( & specifier )
2023-03-23 14:23:04 +00:00
. unwrap_or_else ( | _ | {
LspClientUrl ::new ( ModuleSpecifier ::parse ( " deno://invalid " ) . unwrap ( ) )
} ) ;
2021-02-08 10:45:10 +00:00
lsp ::Location {
2023-03-23 14:23:04 +00:00
uri : uri . into_url ( ) ,
2021-02-08 10:45:10 +00:00
range : self . document_span . text_span . to_range ( line_index ) ,
}
}
2021-02-18 03:15:13 +00:00
2022-04-25 15:23:24 +00:00
pub fn to_link (
2021-02-18 03:15:13 +00:00
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2022-01-19 16:38:40 +00:00
language_server : & language_server ::Inner ,
2021-02-18 03:15:13 +00:00
) -> Option < lsp ::LocationLink > {
2022-04-25 15:23:24 +00:00
self . document_span . to_link ( line_index , language_server )
2021-02-18 03:15:13 +00:00
}
2021-02-08 10:45:10 +00:00
}
2020-12-30 00:58:20 +00:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct RenameLocation {
2021-01-16 12:00:42 +00:00
#[ serde(flatten) ]
document_span : DocumentSpan ,
2020-12-30 00:58:20 +00:00
// RenameLocation props
2022-02-23 21:01:20 +00:00
// prefix_text: Option<String>,
// suffix_text: Option<String>,
2020-12-30 00:58:20 +00:00
}
2023-09-29 19:44:59 +00:00
impl RenameLocation {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . document_span . normalize ( specifier_map ) ? ;
Ok ( ( ) )
}
}
2020-12-30 00:58:20 +00:00
pub struct RenameLocations {
pub locations : Vec < RenameLocation > ,
}
impl RenameLocations {
2024-03-12 03:48:00 +00:00
pub fn into_workspace_edit (
2020-12-30 00:58:20 +00:00
self ,
new_name : & str ,
2022-01-19 16:38:40 +00:00
language_server : & language_server ::Inner ,
2021-02-06 12:39:01 +00:00
) -> Result < lsp ::WorkspaceEdit , AnyError > {
2023-03-23 14:23:04 +00:00
let mut text_document_edit_map : HashMap <
LspClientUrl ,
lsp ::TextDocumentEdit ,
> = HashMap ::new ( ) ;
2024-03-07 17:27:24 +00:00
let mut includes_non_files = false ;
2020-12-30 00:58:20 +00:00
for location in self . locations . iter ( ) {
2023-10-02 06:32:05 +00:00
let specifier = resolve_url ( & location . document_span . file_name ) ? ;
2024-03-07 17:27:24 +00:00
if specifier . scheme ( ) ! = " file " {
includes_non_files = true ;
continue ;
}
2021-02-18 04:37:05 +00:00
let uri = language_server . url_map . normalize_specifier ( & specifier ) ? ;
2022-04-25 15:23:24 +00:00
let asset_or_doc = language_server . get_asset_or_document ( & specifier ) ? ;
2020-12-30 00:58:20 +00:00
// ensure TextDocumentEdit for `location.file_name`.
if text_document_edit_map . get ( & uri ) . is_none ( ) {
text_document_edit_map . insert (
uri . clone ( ) ,
2021-01-29 19:34:33 +00:00
lsp ::TextDocumentEdit {
text_document : lsp ::OptionalVersionedTextDocumentIdentifier {
2023-03-23 14:23:04 +00:00
uri : uri . as_url ( ) . clone ( ) ,
2021-11-18 18:50:24 +00:00
version : asset_or_doc . document_lsp_version ( ) ,
2020-12-30 00:58:20 +00:00
} ,
2021-01-29 19:34:33 +00:00
edits :
Vec ::< lsp ::OneOf < lsp ::TextEdit , lsp ::AnnotatedTextEdit > > ::new ( ) ,
2020-12-30 00:58:20 +00:00
} ,
) ;
}
// push TextEdit for ensured `TextDocumentEdit.edits`.
let document_edit = text_document_edit_map . get_mut ( & uri ) . unwrap ( ) ;
2021-01-29 19:34:33 +00:00
document_edit . edits . push ( lsp ::OneOf ::Left ( lsp ::TextEdit {
range : location
. document_span
. text_span
2021-11-12 16:42:04 +00:00
. to_range ( asset_or_doc . line_index ( ) ) ,
2021-01-29 19:34:33 +00:00
new_text : new_name . to_string ( ) ,
} ) ) ;
2020-12-30 00:58:20 +00:00
}
2024-03-07 17:27:24 +00:00
if includes_non_files {
language_server . client . show_message ( lsp ::MessageType ::WARNING , " The renamed symbol had references in non-file schemed modules. These have not been modified. " ) ;
}
2021-01-29 19:34:33 +00:00
Ok ( lsp ::WorkspaceEdit {
2021-01-12 07:50:02 +00:00
change_annotations : None ,
2020-12-30 00:58:20 +00:00
changes : None ,
2021-01-29 19:34:33 +00:00
document_changes : Some ( lsp ::DocumentChanges ::Edits (
2020-12-30 00:58:20 +00:00
text_document_edit_map . values ( ) . cloned ( ) . collect ( ) ,
) ) ,
} )
}
}
2020-12-07 10:46:39 +00:00
#[ derive(Debug, Deserialize) ]
pub enum HighlightSpanKind {
#[ serde(rename = " none " ) ]
None ,
#[ serde(rename = " definition " ) ]
Definition ,
#[ serde(rename = " reference " ) ]
Reference ,
#[ serde(rename = " writtenReference " ) ]
WrittenReference ,
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct HighlightSpan {
2022-02-23 21:01:20 +00:00
// file_name: Option<String>,
// is_in_string: Option<bool>,
2020-12-07 10:46:39 +00:00
text_span : TextSpan ,
2022-02-23 21:01:20 +00:00
// context_span: Option<TextSpan>,
2020-12-07 10:46:39 +00:00
kind : HighlightSpanKind ,
}
2023-09-29 19:44:59 +00:00
#[ derive(Debug, Deserialize, Serialize) ]
2020-12-07 10:46:39 +00:00
#[ serde(rename_all = " camelCase " ) ]
pub struct DefinitionInfo {
2022-02-23 21:01:20 +00:00
// kind: ScriptElementKind,
// name: String,
// container_kind: Option<ScriptElementKind>,
// container_name: Option<String>,
2021-01-12 21:53:27 +00:00
#[ serde(flatten) ]
pub document_span : DocumentSpan ,
2020-12-07 10:46:39 +00:00
}
2023-09-29 19:44:59 +00:00
impl DefinitionInfo {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . document_span . normalize ( specifier_map ) ? ;
Ok ( ( ) )
}
}
2020-12-07 10:46:39 +00:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct DefinitionInfoAndBoundSpan {
pub definitions : Option < Vec < DefinitionInfo > > ,
2022-02-23 21:01:20 +00:00
// text_span: TextSpan,
2020-12-07 10:46:39 +00:00
}
impl DefinitionInfoAndBoundSpan {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
for definition in self . definitions . iter_mut ( ) . flatten ( ) {
definition . normalize ( specifier_map ) ? ;
}
Ok ( ( ) )
}
2024-03-12 03:48:00 +00:00
pub fn to_definition (
2020-12-07 10:46:39 +00:00
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2022-01-19 16:38:40 +00:00
language_server : & language_server ::Inner ,
2021-02-06 12:39:01 +00:00
) -> Option < lsp ::GotoDefinitionResponse > {
2020-12-07 10:46:39 +00:00
if let Some ( definitions ) = & self . definitions {
2021-01-29 19:34:33 +00:00
let mut location_links = Vec ::< lsp ::LocationLink > ::new ( ) ;
2020-12-21 13:44:26 +00:00
for di in definitions {
2021-10-28 23:56:01 +00:00
if let Some ( link ) = di
. document_span
. to_link ( line_index . clone ( ) , language_server )
2021-01-12 21:53:27 +00:00
{
location_links . push ( link ) ;
2020-12-21 13:44:26 +00:00
}
}
2021-01-29 19:34:33 +00:00
Some ( lsp ::GotoDefinitionResponse ::Link ( location_links ) )
2020-12-07 10:46:39 +00:00
} else {
None
}
}
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct DocumentHighlights {
2022-02-23 21:01:20 +00:00
// file_name: String,
2020-12-07 10:46:39 +00:00
highlight_spans : Vec < HighlightSpan > ,
}
impl DocumentHighlights {
pub fn to_highlight (
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2021-01-29 19:34:33 +00:00
) -> Vec < lsp ::DocumentHighlight > {
2020-12-07 10:46:39 +00:00
self
. highlight_spans
. iter ( )
2021-01-29 19:34:33 +00:00
. map ( | hs | lsp ::DocumentHighlight {
2021-10-28 23:56:01 +00:00
range : hs . text_span . to_range ( line_index . clone ( ) ) ,
2020-12-07 10:46:39 +00:00
kind : match hs . kind {
HighlightSpanKind ::WrittenReference = > {
2021-11-25 01:10:12 +00:00
Some ( lsp ::DocumentHighlightKind ::WRITE )
2020-12-07 10:46:39 +00:00
}
2021-11-25 01:10:12 +00:00
_ = > Some ( lsp ::DocumentHighlightKind ::READ ) ,
2020-12-07 10:46:39 +00:00
} ,
} )
. collect ( )
}
}
2021-02-04 18:53:02 +00:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct TextChange {
2021-05-29 11:21:11 +00:00
pub span : TextSpan ,
pub new_text : String ,
2021-02-04 18:53:02 +00:00
}
impl TextChange {
2022-07-11 23:35:18 +00:00
pub fn as_text_edit ( & self , line_index : Arc < LineIndex > ) -> lsp ::TextEdit {
lsp ::TextEdit {
range : self . span . to_range ( line_index ) ,
new_text : self . new_text . clone ( ) ,
}
}
pub fn as_text_or_annotated_text_edit (
2021-02-04 18:53:02 +00:00
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2021-02-04 18:53:02 +00:00
) -> lsp ::OneOf < lsp ::TextEdit , lsp ::AnnotatedTextEdit > {
lsp ::OneOf ::Left ( lsp ::TextEdit {
range : self . span . to_range ( line_index ) ,
new_text : self . new_text . clone ( ) ,
} )
}
}
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct FileTextChanges {
2021-05-29 11:21:11 +00:00
pub file_name : String ,
pub text_changes : Vec < TextChange > ,
2021-02-04 18:53:02 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2021-05-29 11:21:11 +00:00
pub is_new_file : Option < bool > ,
2021-02-04 18:53:02 +00:00
}
impl FileTextChanges {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . file_name = specifier_map . normalize ( & self . file_name ) ? . to_string ( ) ;
Ok ( ( ) )
}
2022-04-25 15:23:24 +00:00
pub fn to_text_document_edit (
2021-02-04 18:53:02 +00:00
& self ,
2022-01-18 21:28:47 +00:00
language_server : & language_server ::Inner ,
2021-02-06 12:39:01 +00:00
) -> Result < lsp ::TextDocumentEdit , AnyError > {
2023-10-02 06:32:05 +00:00
let specifier = resolve_url ( & self . file_name ) ? ;
2022-04-25 15:23:24 +00:00
let asset_or_doc = language_server . get_asset_or_document ( & specifier ) ? ;
2021-02-04 18:53:02 +00:00
let edits = self
. text_changes
. iter ( )
2022-07-11 23:35:18 +00:00
. map ( | tc | tc . as_text_or_annotated_text_edit ( asset_or_doc . line_index ( ) ) )
2021-02-04 18:53:02 +00:00
. collect ( ) ;
Ok ( lsp ::TextDocumentEdit {
text_document : lsp ::OptionalVersionedTextDocumentIdentifier {
2022-04-25 15:23:24 +00:00
uri : specifier ,
2021-11-18 18:50:24 +00:00
version : asset_or_doc . document_lsp_version ( ) ,
2021-02-04 18:53:02 +00:00
} ,
edits ,
} )
}
2021-08-06 01:46:32 +00:00
2022-04-25 15:23:24 +00:00
pub fn to_text_document_change_ops (
2021-08-06 01:46:32 +00:00
& self ,
2022-01-18 21:28:47 +00:00
language_server : & language_server ::Inner ,
2021-08-06 01:46:32 +00:00
) -> Result < Vec < lsp ::DocumentChangeOperation > , AnyError > {
let mut ops = Vec ::< lsp ::DocumentChangeOperation > ::new ( ) ;
2023-10-02 06:32:05 +00:00
let specifier = resolve_url ( & self . file_name ) ? ;
2021-11-12 16:42:04 +00:00
let maybe_asset_or_document = if ! self . is_new_file . unwrap_or ( false ) {
2022-04-25 15:23:24 +00:00
let asset_or_doc = language_server . get_asset_or_document ( & specifier ) ? ;
2021-11-12 16:42:04 +00:00
Some ( asset_or_doc )
2021-08-06 01:46:32 +00:00
} else {
2021-11-12 16:42:04 +00:00
None
2021-08-06 01:46:32 +00:00
} ;
2021-11-12 16:42:04 +00:00
let line_index = maybe_asset_or_document
. as_ref ( )
. map ( | d | d . line_index ( ) )
. unwrap_or_else ( | | Arc ::new ( LineIndex ::new ( " " ) ) ) ;
2021-08-06 01:46:32 +00:00
if self . is_new_file . unwrap_or ( false ) {
ops . push ( lsp ::DocumentChangeOperation ::Op ( lsp ::ResourceOp ::Create (
lsp ::CreateFile {
uri : specifier . clone ( ) ,
options : Some ( lsp ::CreateFileOptions {
ignore_if_exists : Some ( true ) ,
overwrite : None ,
} ) ,
annotation_id : None ,
} ,
) ) ) ;
}
let edits = self
. text_changes
. iter ( )
2022-07-11 23:35:18 +00:00
. map ( | tc | tc . as_text_or_annotated_text_edit ( line_index . clone ( ) ) )
2021-08-06 01:46:32 +00:00
. collect ( ) ;
ops . push ( lsp ::DocumentChangeOperation ::Edit ( lsp ::TextDocumentEdit {
text_document : lsp ::OptionalVersionedTextDocumentIdentifier {
2022-04-25 15:23:24 +00:00
uri : specifier ,
2022-02-25 01:03:12 +00:00
version : maybe_asset_or_document . and_then ( | d | d . document_lsp_version ( ) ) ,
2021-08-06 01:46:32 +00:00
} ,
edits ,
} ) ) ;
Ok ( ops )
}
2021-02-04 18:53:02 +00:00
}
2021-04-20 01:26:36 +00:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Classifications {
spans : Vec < u32 > ,
}
impl Classifications {
pub fn to_semantic_tokens (
& self ,
2022-03-02 21:06:38 +00:00
asset_or_doc : & AssetOrDocument ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2021-11-16 22:23:25 +00:00
) -> LspResult < lsp ::SemanticTokens > {
2021-04-20 01:26:36 +00:00
let token_count = self . spans . len ( ) / 3 ;
let mut builder = SemanticTokensBuilder ::new ( ) ;
for i in 0 .. token_count {
let src_offset = 3 * i ;
let offset = self . spans [ src_offset ] ;
let length = self . spans [ src_offset + 1 ] ;
let ts_classification = self . spans [ src_offset + 2 ] ;
let token_type =
Classifications ::get_token_type_from_classification ( ts_classification ) ;
let token_modifiers =
Classifications ::get_token_modifier_from_classification (
ts_classification ,
) ;
let start_pos = line_index . position_tsc ( offset . into ( ) ) ;
let end_pos = line_index . position_tsc ( TextSize ::from ( offset + length ) ) ;
2021-11-16 22:23:25 +00:00
if start_pos . line = = end_pos . line
& & start_pos . character < = end_pos . character
{
builder . push (
start_pos . line ,
start_pos . character ,
end_pos . character - start_pos . character ,
token_type ,
token_modifiers ,
) ;
} else {
log ::error! (
2022-03-02 21:06:38 +00:00
" unexpected positions \n specifier: {} \n open: {} \n start_pos: {:?} \n end_pos: {:?} " ,
asset_or_doc . specifier ( ) ,
asset_or_doc . is_open ( ) ,
2021-11-16 22:23:25 +00:00
start_pos ,
end_pos
) ;
return Err ( LspError ::internal_error ( ) ) ;
}
2021-04-20 01:26:36 +00:00
}
2021-11-16 22:23:25 +00:00
Ok ( builder . build ( None ) )
2021-04-20 01:26:36 +00:00
}
fn get_token_type_from_classification ( ts_classification : u32 ) -> u32 {
2021-11-23 00:08:56 +00:00
assert! ( ts_classification > semantic_tokens ::MODIFIER_MASK ) ;
( ts_classification > > semantic_tokens ::TYPE_OFFSET ) - 1
2021-04-20 01:26:36 +00:00
}
fn get_token_modifier_from_classification ( ts_classification : u32 ) -> u32 {
2021-11-23 00:08:56 +00:00
ts_classification & semantic_tokens ::MODIFIER_MASK
2021-04-20 01:26:36 +00:00
}
}
2021-08-06 01:46:32 +00:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct RefactorActionInfo {
name : String ,
description : String ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
not_applicable_reason : Option < String > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
kind : Option < String > ,
}
impl RefactorActionInfo {
pub fn get_action_kind ( & self ) -> lsp ::CodeActionKind {
if let Some ( kind ) = & self . kind {
kind . clone ( ) . into ( )
} else {
let maybe_match = ALL_KNOWN_REFACTOR_ACTION_KINDS
. iter ( )
. find ( | action | action . matches ( & self . name ) ) ;
maybe_match
2023-03-15 21:46:36 +00:00
. map ( | action | action . kind . clone ( ) )
. unwrap_or ( lsp ::CodeActionKind ::REFACTOR )
2021-08-06 01:46:32 +00:00
}
}
pub fn is_preferred ( & self , all_actions : & [ RefactorActionInfo ] ) -> bool {
if EXTRACT_CONSTANT . matches ( & self . name ) {
let get_scope = | name : & str | -> Option < u32 > {
2021-09-16 02:07:52 +00:00
if let Some ( captures ) = SCOPE_RE . captures ( name ) {
2021-08-06 01:46:32 +00:00
captures [ 1 ] . parse ::< u32 > ( ) . ok ( )
} else {
None
}
} ;
return if let Some ( scope ) = get_scope ( & self . name ) {
all_actions
. iter ( )
. filter ( | other | {
! std ::ptr ::eq ( & self , other ) & & EXTRACT_CONSTANT . matches ( & other . name )
} )
. all ( | other | {
if let Some ( other_scope ) = get_scope ( & other . name ) {
scope < other_scope
} else {
true
}
} )
} else {
false
} ;
}
if EXTRACT_TYPE . matches ( & self . name ) | | EXTRACT_INTERFACE . matches ( & self . name )
{
return true ;
}
false
}
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ApplicableRefactorInfo {
name : String ,
2022-02-23 21:01:20 +00:00
// description: String,
// #[serde(skip_serializing_if = "Option::is_none")]
// inlineable: Option<bool>,
2021-08-06 01:46:32 +00:00
actions : Vec < RefactorActionInfo > ,
}
impl ApplicableRefactorInfo {
pub fn to_code_actions (
& self ,
specifier : & ModuleSpecifier ,
range : & lsp ::Range ,
) -> Vec < lsp ::CodeAction > {
let mut code_actions = Vec ::< lsp ::CodeAction > ::new ( ) ;
// All typescript refactoring actions are inlineable
for action in self . actions . iter ( ) {
code_actions
. push ( self . as_inline_code_action ( action , specifier , range , & self . name ) ) ;
}
code_actions
}
fn as_inline_code_action (
& self ,
action : & RefactorActionInfo ,
specifier : & ModuleSpecifier ,
range : & lsp ::Range ,
refactor_name : & str ,
) -> lsp ::CodeAction {
let disabled = action . not_applicable_reason . as_ref ( ) . map ( | reason | {
lsp ::CodeActionDisabled {
reason : reason . clone ( ) ,
}
} ) ;
lsp ::CodeAction {
title : action . description . to_string ( ) ,
kind : Some ( action . get_action_kind ( ) ) ,
is_preferred : Some ( action . is_preferred ( & self . actions ) ) ,
disabled ,
data : Some (
serde_json ::to_value ( RefactorCodeActionData {
specifier : specifier . clone ( ) ,
range : * range ,
refactor_name : refactor_name . to_owned ( ) ,
action_name : action . name . clone ( ) ,
} )
. unwrap ( ) ,
) ,
.. Default ::default ( )
}
}
}
2023-08-26 00:50:47 +00:00
pub fn file_text_changes_to_workspace_edit (
changes : & [ FileTextChanges ] ,
language_server : & language_server ::Inner ,
) -> LspResult < Option < lsp ::WorkspaceEdit > > {
let mut all_ops = Vec ::< lsp ::DocumentChangeOperation > ::new ( ) ;
for change in changes {
let ops = match change . to_text_document_change_ops ( language_server ) {
Ok ( op ) = > op ,
Err ( err ) = > {
error! ( " Unable to convert changes to edits: {} " , err ) ;
return Err ( LspError ::internal_error ( ) ) ;
}
} ;
all_ops . extend ( ops ) ;
}
Ok ( Some ( lsp ::WorkspaceEdit {
document_changes : Some ( lsp ::DocumentChanges ::Operations ( all_ops ) ) ,
.. Default ::default ( )
} ) )
}
2023-09-29 19:44:59 +00:00
#[ derive(Debug, Deserialize, Serialize) ]
2021-08-06 01:46:32 +00:00
#[ serde(rename_all = " camelCase " ) ]
pub struct RefactorEditInfo {
edits : Vec < FileTextChanges > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub rename_location : Option < u32 > ,
}
impl RefactorEditInfo {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
for changes in & mut self . edits {
changes . normalize ( specifier_map ) ? ;
}
Ok ( ( ) )
}
2024-03-12 03:48:00 +00:00
pub fn to_workspace_edit (
2021-08-06 01:46:32 +00:00
& self ,
2022-01-18 21:28:47 +00:00
language_server : & language_server ::Inner ,
2023-08-26 00:50:47 +00:00
) -> LspResult < Option < lsp ::WorkspaceEdit > > {
file_text_changes_to_workspace_edit ( & self . edits , language_server )
2021-08-06 01:46:32 +00:00
}
}
2022-07-11 23:35:18 +00:00
#[ derive(Debug, Deserialize, Serialize) ]
2021-03-15 22:01:41 +00:00
#[ serde(rename_all = " camelCase " ) ]
pub struct CodeAction {
2022-07-11 23:35:18 +00:00
description : String ,
changes : Vec < FileTextChanges > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
commands : Option < Vec < Value > > ,
2021-03-15 22:01:41 +00:00
}
2023-09-29 19:44:59 +00:00
impl CodeAction {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
for changes in & mut self . changes {
changes . normalize ( specifier_map ) ? ;
}
Ok ( ( ) )
}
}
2021-02-04 18:53:02 +00:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CodeFixAction {
pub description : String ,
pub changes : Vec < FileTextChanges > ,
// These are opaque types that should just be passed back when applying the
// action.
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub commands : Option < Vec < Value > > ,
pub fix_name : String ,
// It appears currently that all fixIds are strings, but the protocol
// specifies an opaque type, the problem is that we need to use the id as a
// hash key, and `Value` does not implement hash (and it could provide a false
// positive depending on JSON whitespace, so we deserialize it but it might
// break in the future)
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub fix_id : Option < String > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub fix_all_description : Option < String > ,
}
2023-09-29 19:44:59 +00:00
impl CodeFixAction {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
for changes in & mut self . changes {
changes . normalize ( specifier_map ) ? ;
}
Ok ( ( ) )
}
}
2021-02-04 18:53:02 +00:00
#[ derive(Debug, Clone, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CombinedCodeActions {
pub changes : Vec < FileTextChanges > ,
pub commands : Option < Vec < Value > > ,
}
2023-09-29 19:44:59 +00:00
impl CombinedCodeActions {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
for changes in & mut self . changes {
changes . normalize ( specifier_map ) ? ;
}
Ok ( ( ) )
}
}
#[ derive(Debug, Deserialize, Serialize) ]
2020-12-07 10:46:39 +00:00
#[ serde(rename_all = " camelCase " ) ]
2023-03-30 16:15:21 +00:00
pub struct ReferencedSymbol {
pub definition : ReferencedSymbolDefinitionInfo ,
pub references : Vec < ReferencedSymbolEntry > ,
}
2023-09-29 19:44:59 +00:00
impl ReferencedSymbol {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . definition . normalize ( specifier_map ) ? ;
for reference in & mut self . references {
reference . normalize ( specifier_map ) ? ;
}
Ok ( ( ) )
}
}
#[ derive(Debug, Deserialize, Serialize) ]
2023-03-30 16:15:21 +00:00
#[ serde(rename_all = " camelCase " ) ]
pub struct ReferencedSymbolDefinitionInfo {
#[ serde(flatten) ]
pub definition_info : DefinitionInfo ,
}
2023-09-29 19:44:59 +00:00
impl ReferencedSymbolDefinitionInfo {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . definition_info . normalize ( specifier_map ) ? ;
Ok ( ( ) )
}
}
#[ derive(Debug, Deserialize, Serialize) ]
2023-03-30 16:15:21 +00:00
#[ serde(rename_all = " camelCase " ) ]
pub struct ReferencedSymbolEntry {
2022-06-01 00:19:18 +00:00
#[ serde(default) ]
2020-12-07 10:46:39 +00:00
pub is_definition : bool ,
2023-03-30 16:15:21 +00:00
#[ serde(flatten) ]
pub entry : ReferenceEntry ,
}
2023-09-29 19:44:59 +00:00
impl ReferencedSymbolEntry {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . entry . normalize ( specifier_map ) ? ;
Ok ( ( ) )
}
}
#[ derive(Debug, Deserialize, Serialize) ]
2023-03-30 16:15:21 +00:00
#[ serde(rename_all = " camelCase " ) ]
pub struct ReferenceEntry {
// is_write_access: bool,
2022-02-23 21:01:20 +00:00
// is_in_string: Option<bool>,
2021-01-16 12:00:42 +00:00
#[ serde(flatten) ]
pub document_span : DocumentSpan ,
2020-12-07 10:46:39 +00:00
}
2023-09-29 19:44:59 +00:00
impl ReferenceEntry {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . document_span . normalize ( specifier_map ) ? ;
Ok ( ( ) )
}
}
2020-12-07 10:46:39 +00:00
impl ReferenceEntry {
2022-03-23 13:54:22 +00:00
pub fn to_location (
2021-02-18 04:37:05 +00:00
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2022-01-19 22:10:14 +00:00
url_map : & LspUrlMap ,
2021-02-18 04:37:05 +00:00
) -> lsp ::Location {
2023-10-02 06:32:05 +00:00
let specifier = resolve_url ( & self . document_span . file_name )
2021-07-25 05:33:42 +00:00
. unwrap_or_else ( | _ | INVALID_SPECIFIER . clone ( ) ) ;
2022-01-19 22:10:14 +00:00
let uri = url_map
2021-02-18 04:37:05 +00:00
. normalize_specifier ( & specifier )
2023-03-23 14:23:04 +00:00
. unwrap_or_else ( | _ | LspClientUrl ::new ( INVALID_SPECIFIER . clone ( ) ) ) ;
2021-01-29 19:34:33 +00:00
lsp ::Location {
2023-03-23 14:23:04 +00:00
uri : uri . into_url ( ) ,
2021-01-16 12:00:42 +00:00
range : self . document_span . text_span . to_range ( line_index ) ,
2020-12-07 10:46:39 +00:00
}
}
}
2021-04-19 05:11:26 +00:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CallHierarchyItem {
name : String ,
kind : ScriptElementKind ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
kind_modifiers : Option < String > ,
file : String ,
span : TextSpan ,
selection_span : TextSpan ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
container_name : Option < String > ,
}
impl CallHierarchyItem {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . file = specifier_map . normalize ( & self . file ) ? . to_string ( ) ;
Ok ( ( ) )
}
2022-04-25 15:23:24 +00:00
pub fn try_resolve_call_hierarchy_item (
2021-04-19 05:11:26 +00:00
& self ,
2022-01-19 16:38:40 +00:00
language_server : & language_server ::Inner ,
2021-04-19 05:11:26 +00:00
maybe_root_path : Option < & Path > ,
) -> Option < lsp ::CallHierarchyItem > {
2023-10-02 06:32:05 +00:00
let target_specifier = resolve_url ( & self . file ) . ok ( ) ? ;
2022-04-25 15:23:24 +00:00
let target_asset_or_doc =
language_server . get_maybe_asset_or_document ( & target_specifier ) ? ;
2021-04-19 05:11:26 +00:00
Some ( self . to_call_hierarchy_item (
2021-11-12 16:42:04 +00:00
target_asset_or_doc . line_index ( ) ,
2021-04-19 05:11:26 +00:00
language_server ,
maybe_root_path ,
) )
}
2022-03-23 13:54:22 +00:00
pub fn to_call_hierarchy_item (
2021-04-19 05:11:26 +00:00
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2022-01-19 16:38:40 +00:00
language_server : & language_server ::Inner ,
2021-04-19 05:11:26 +00:00
maybe_root_path : Option < & Path > ,
) -> lsp ::CallHierarchyItem {
2023-10-02 06:32:05 +00:00
let target_specifier =
resolve_url ( & self . file ) . unwrap_or_else ( | _ | INVALID_SPECIFIER . clone ( ) ) ;
2021-04-19 05:11:26 +00:00
let uri = language_server
. url_map
. normalize_specifier ( & target_specifier )
2023-03-23 14:23:04 +00:00
. unwrap_or_else ( | _ | LspClientUrl ::new ( INVALID_SPECIFIER . clone ( ) ) ) ;
2021-04-19 05:11:26 +00:00
let use_file_name = self . is_source_file_item ( ) ;
2023-03-23 14:23:04 +00:00
let maybe_file_path = if uri . as_url ( ) . scheme ( ) = = " file " {
specifier_to_file_path ( uri . as_url ( ) ) . ok ( )
2021-04-19 05:11:26 +00:00
} else {
None
} ;
let name = if use_file_name {
if let Some ( file_path ) = maybe_file_path . as_ref ( ) {
file_path . file_name ( ) . unwrap ( ) . to_string_lossy ( ) . to_string ( )
} else {
2023-03-23 14:23:04 +00:00
uri . as_str ( ) . to_string ( )
2021-04-19 05:11:26 +00:00
}
} else {
self . name . clone ( )
} ;
let detail = if use_file_name {
if let Some ( file_path ) = maybe_file_path . as_ref ( ) {
// TODO: update this to work with multi root workspaces
let parent_dir = file_path . parent ( ) . unwrap ( ) ;
if let Some ( root_path ) = maybe_root_path {
parent_dir
. strip_prefix ( root_path )
. unwrap_or ( parent_dir )
. to_string_lossy ( )
. to_string ( )
} else {
parent_dir . to_string_lossy ( ) . to_string ( )
}
} else {
String ::new ( )
}
} else {
self . container_name . as_ref ( ) . cloned ( ) . unwrap_or_default ( )
} ;
let mut tags : Option < Vec < lsp ::SymbolTag > > = None ;
if let Some ( modifiers ) = self . kind_modifiers . as_ref ( ) {
let kind_modifiers = parse_kind_modifier ( modifiers ) ;
if kind_modifiers . contains ( " deprecated " ) {
2021-11-25 01:10:12 +00:00
tags = Some ( vec! [ lsp ::SymbolTag ::DEPRECATED ] ) ;
2021-04-19 05:11:26 +00:00
}
}
lsp ::CallHierarchyItem {
name ,
tags ,
2023-03-23 14:23:04 +00:00
uri : uri . into_url ( ) ,
2021-04-19 05:11:26 +00:00
detail : Some ( detail ) ,
kind : self . kind . clone ( ) . into ( ) ,
2021-10-28 23:56:01 +00:00
range : self . span . to_range ( line_index . clone ( ) ) ,
2021-04-19 05:11:26 +00:00
selection_range : self . selection_span . to_range ( line_index ) ,
data : None ,
}
}
fn is_source_file_item ( & self ) -> bool {
self . kind = = ScriptElementKind ::ScriptElement
| | self . kind = = ScriptElementKind ::ModuleElement
& & self . selection_span . start = = 0
}
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CallHierarchyIncomingCall {
from : CallHierarchyItem ,
from_spans : Vec < TextSpan > ,
}
impl CallHierarchyIncomingCall {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . from . normalize ( specifier_map ) ? ;
Ok ( ( ) )
}
2022-04-25 15:23:24 +00:00
pub fn try_resolve_call_hierarchy_incoming_call (
2021-04-19 05:11:26 +00:00
& self ,
2022-01-19 16:38:40 +00:00
language_server : & language_server ::Inner ,
2021-04-19 05:11:26 +00:00
maybe_root_path : Option < & Path > ,
) -> Option < lsp ::CallHierarchyIncomingCall > {
2023-10-02 06:32:05 +00:00
let target_specifier = resolve_url ( & self . from . file ) . ok ( ) ? ;
2022-04-25 15:23:24 +00:00
let target_asset_or_doc =
language_server . get_maybe_asset_or_document ( & target_specifier ) ? ;
2021-04-19 05:11:26 +00:00
Some ( lsp ::CallHierarchyIncomingCall {
from : self . from . to_call_hierarchy_item (
2021-11-12 16:42:04 +00:00
target_asset_or_doc . line_index ( ) ,
2021-04-19 05:11:26 +00:00
language_server ,
maybe_root_path ,
) ,
from_ranges : self
. from_spans
. iter ( )
2021-11-12 16:42:04 +00:00
. map ( | span | span . to_range ( target_asset_or_doc . line_index ( ) ) )
2021-04-19 05:11:26 +00:00
. collect ( ) ,
} )
}
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CallHierarchyOutgoingCall {
to : CallHierarchyItem ,
from_spans : Vec < TextSpan > ,
}
impl CallHierarchyOutgoingCall {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
self . to . normalize ( specifier_map ) ? ;
Ok ( ( ) )
}
2022-04-25 15:23:24 +00:00
pub fn try_resolve_call_hierarchy_outgoing_call (
2021-04-19 05:11:26 +00:00
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2022-01-19 16:38:40 +00:00
language_server : & language_server ::Inner ,
2021-04-19 05:11:26 +00:00
maybe_root_path : Option < & Path > ,
) -> Option < lsp ::CallHierarchyOutgoingCall > {
2023-10-02 06:32:05 +00:00
let target_specifier = resolve_url ( & self . to . file ) . ok ( ) ? ;
2022-04-25 15:23:24 +00:00
let target_asset_or_doc =
language_server . get_maybe_asset_or_document ( & target_specifier ) ? ;
2021-04-19 05:11:26 +00:00
Some ( lsp ::CallHierarchyOutgoingCall {
to : self . to . to_call_hierarchy_item (
2021-11-12 16:42:04 +00:00
target_asset_or_doc . line_index ( ) ,
2021-04-19 05:11:26 +00:00
language_server ,
maybe_root_path ,
) ,
from_ranges : self
. from_spans
. iter ( )
2021-10-28 23:56:01 +00:00
. map ( | span | span . to_range ( line_index . clone ( ) ) )
2021-04-19 05:11:26 +00:00
. collect ( ) ,
} )
}
}
2022-07-11 23:35:18 +00:00
/// Used to convert completion code actions into a command and additional text
/// edits to pass in the completion item.
fn parse_code_actions (
maybe_code_actions : Option < & Vec < CodeAction > > ,
data : & CompletionItemData ,
specifier : & ModuleSpecifier ,
language_server : & language_server ::Inner ,
) -> Result < ( Option < lsp ::Command > , Option < Vec < lsp ::TextEdit > > ) , AnyError > {
if let Some ( code_actions ) = maybe_code_actions {
let mut additional_text_edits : Vec < lsp ::TextEdit > = Vec ::new ( ) ;
let mut has_remaining_commands_or_edits = false ;
for ts_action in code_actions {
if ts_action . commands . is_some ( ) {
has_remaining_commands_or_edits = true ;
}
let asset_or_doc =
language_server . get_asset_or_document ( & data . specifier ) ? ;
for change in & ts_action . changes {
2023-10-02 06:32:05 +00:00
if data . specifier . as_str ( ) = = change . file_name {
2022-07-11 23:35:18 +00:00
additional_text_edits . extend ( change . text_changes . iter ( ) . map ( | tc | {
2023-09-18 23:59:26 +00:00
let mut text_edit = tc . as_text_edit ( asset_or_doc . line_index ( ) ) ;
if let Some ( specifier_rewrite ) = & data . specifier_rewrite {
text_edit . new_text = text_edit
. new_text
. replace ( & specifier_rewrite . 0 , & specifier_rewrite . 1 ) ;
}
text_edit
2022-07-11 23:35:18 +00:00
} ) ) ;
} else {
has_remaining_commands_or_edits = true ;
}
}
}
let mut command : Option < lsp ::Command > = None ;
if has_remaining_commands_or_edits {
let actions : Vec < Value > = code_actions
. iter ( )
. map ( | ca | {
let changes : Vec < FileTextChanges > = ca
. changes
. clone ( )
. into_iter ( )
2023-10-02 06:32:05 +00:00
. filter ( | ch | ch . file_name = = data . specifier . as_str ( ) )
2022-07-11 23:35:18 +00:00
. collect ( ) ;
json! ( {
" commands " : ca . commands ,
" description " : ca . description ,
" changes " : changes ,
} )
} )
. collect ( ) ;
command = Some ( lsp ::Command {
title : " " . to_string ( ) ,
command : " _typescript.applyCompletionCodeAction " . to_string ( ) ,
arguments : Some ( vec! [ json! ( specifier . to_string ( ) ) , json! ( actions ) ] ) ,
} ) ;
}
if additional_text_edits . is_empty ( ) {
Ok ( ( command , None ) )
} else {
Ok ( ( command , Some ( additional_text_edits ) ) )
}
} else {
Ok ( ( None , None ) )
}
}
2023-08-26 00:53:44 +00:00
// Based on https://github.com/microsoft/vscode/blob/1.81.1/extensions/typescript-language-features/src/languageFeatures/util/snippetForFunctionCall.ts#L49.
fn get_parameters_from_parts ( parts : & [ SymbolDisplayPart ] ) -> Vec < String > {
let mut parameters = Vec ::with_capacity ( 3 ) ;
let mut is_in_fn = false ;
let mut paren_count = 0 ;
let mut brace_count = 0 ;
for ( idx , part ) in parts . iter ( ) . enumerate ( ) {
if [ " methodName " , " functionName " , " text " , " propertyName " ]
. contains ( & part . kind . as_str ( ) )
{
if paren_count = = 0 & & brace_count = = 0 {
is_in_fn = true ;
}
} else if part . kind = = " parameterName " {
if paren_count = = 1 & & brace_count = = 0 & & is_in_fn {
let is_optional =
matches! ( parts . get ( idx + 1 ) , Some ( next ) if next . text = = " ? " ) ;
// Skip `this` and optional parameters.
if ! is_optional & & part . text ! = " this " {
2024-01-26 13:41:12 +00:00
parameters . push ( format! (
" ${{{}:{}}} " ,
parameters . len ( ) + 1 ,
& part . text
) ) ;
2023-08-26 00:53:44 +00:00
}
}
} else if part . kind = = " punctuation " {
if part . text = = " ( " {
paren_count + = 1 ;
} else if part . text = = " ) " {
paren_count - = 1 ;
if paren_count < = 0 & & is_in_fn {
break ;
}
} else if part . text = = " ... " & & paren_count = = 1 {
2024-04-30 07:59:56 +00:00
// Found rest parameter. Do not fill in any further arguments.
2023-08-26 00:53:44 +00:00
break ;
} else if part . text = = " { " {
brace_count + = 1 ;
} else if part . text = = " } " {
brace_count - = 1 ;
}
}
}
parameters
}
2022-07-11 23:35:18 +00:00
#[ derive(Debug, Deserialize, Serialize) ]
2020-12-08 10:36:13 +00:00
#[ serde(rename_all = " camelCase " ) ]
2021-03-15 22:01:41 +00:00
pub struct CompletionEntryDetails {
display_parts : Vec < SymbolDisplayPart > ,
documentation : Option < Vec < SymbolDisplayPart > > ,
2023-10-02 06:32:05 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2021-03-25 18:17:37 +00:00
tags : Option < Vec < JsDocTagInfo > > ,
2022-07-11 23:35:18 +00:00
name : String ,
kind : ScriptElementKind ,
kind_modifiers : String ,
2023-10-02 06:32:05 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2022-07-11 23:35:18 +00:00
code_actions : Option < Vec < CodeAction > > ,
2023-10-02 06:32:05 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2022-07-11 23:35:18 +00:00
source_display : Option < Vec < SymbolDisplayPart > > ,
2021-03-15 22:01:41 +00:00
}
impl CompletionEntryDetails {
2023-10-02 06:32:05 +00:00
fn normalize (
2023-09-29 19:44:59 +00:00
& mut self ,
specifier_map : & TscSpecifierMap ,
) -> Result < ( ) , AnyError > {
for action in self . code_actions . iter_mut ( ) . flatten ( ) {
action . normalize ( specifier_map ) ? ;
}
Ok ( ( ) )
}
2022-03-23 13:54:22 +00:00
pub fn as_completion_item (
2021-03-15 22:01:41 +00:00
& self ,
original_item : & lsp ::CompletionItem ,
2022-07-11 23:35:18 +00:00
data : & CompletionItemData ,
specifier : & ModuleSpecifier ,
2022-02-09 23:08:53 +00:00
language_server : & language_server ::Inner ,
2022-07-11 23:35:18 +00:00
) -> Result < lsp ::CompletionItem , AnyError > {
2021-03-15 22:01:41 +00:00
let detail = if original_item . detail . is_some ( ) {
original_item . detail . clone ( )
} else if ! self . display_parts . is_empty ( ) {
2022-12-17 22:20:15 +00:00
Some ( replace_links ( display_parts_to_string (
2022-02-09 23:08:53 +00:00
& self . display_parts ,
language_server ,
) ) )
2021-03-15 22:01:41 +00:00
} else {
None
} ;
let documentation = if let Some ( parts ) = & self . documentation {
2022-02-09 23:08:53 +00:00
let mut value = display_parts_to_string ( parts , language_server ) ;
2021-03-15 22:01:41 +00:00
if let Some ( tags ) = & self . tags {
let tag_documentation = tags
. iter ( )
2022-02-09 23:08:53 +00:00
. map ( | tag_info | get_tag_documentation ( tag_info , language_server ) )
2021-03-15 22:01:41 +00:00
. collect ::< Vec < String > > ( )
. join ( " " ) ;
2023-01-27 15:43:16 +00:00
value = format! ( " {value} \n \n {tag_documentation} " ) ;
2021-03-15 22:01:41 +00:00
}
Some ( lsp ::Documentation ::MarkupContent ( lsp ::MarkupContent {
kind : lsp ::MarkupKind ::Markdown ,
value ,
} ) )
} else {
None
} ;
2023-12-15 15:24:38 +00:00
let mut text_edit = original_item . text_edit . clone ( ) ;
if let Some ( specifier_rewrite ) = & data . specifier_rewrite {
if let Some ( text_edit ) = & mut text_edit {
match text_edit {
lsp ::CompletionTextEdit ::Edit ( text_edit ) = > {
text_edit . new_text = text_edit
. new_text
. replace ( & specifier_rewrite . 0 , & specifier_rewrite . 1 ) ;
}
lsp ::CompletionTextEdit ::InsertAndReplace ( insert_replace_edit ) = > {
insert_replace_edit . new_text = insert_replace_edit
. new_text
. replace ( & specifier_rewrite . 0 , & specifier_rewrite . 1 ) ;
}
}
}
}
2022-07-11 23:35:18 +00:00
let ( command , additional_text_edits ) = parse_code_actions (
self . code_actions . as_ref ( ) ,
data ,
specifier ,
language_server ,
) ? ;
2024-01-26 13:41:12 +00:00
let mut insert_text_format = original_item . insert_text_format ;
2023-08-26 00:53:44 +00:00
let insert_text = if data . use_code_snippet {
2024-01-26 13:41:12 +00:00
insert_text_format = Some ( lsp ::InsertTextFormat ::SNIPPET ) ;
2023-08-26 00:53:44 +00:00
Some ( format! (
" {}({}) " ,
original_item
. insert_text
. as_ref ( )
. unwrap_or ( & original_item . label ) ,
get_parameters_from_parts ( & self . display_parts ) . join ( " , " ) ,
) )
} else {
original_item . insert_text . clone ( )
} ;
2021-03-15 22:01:41 +00:00
2022-07-11 23:35:18 +00:00
Ok ( lsp ::CompletionItem {
2021-03-15 22:01:41 +00:00
data : None ,
detail ,
documentation ,
2022-07-11 23:35:18 +00:00
command ,
2023-12-15 15:24:38 +00:00
text_edit ,
2022-07-11 23:35:18 +00:00
additional_text_edits ,
2023-08-26 00:53:44 +00:00
insert_text ,
2024-01-26 13:41:12 +00:00
insert_text_format ,
2022-12-30 13:11:50 +00:00
// NOTE(bartlomieju): it's not entirely clear to me why we need to do that,
// but when `completionItem/resolve` is called, we get a list of commit chars
// even though we might have returned an empty list in `completion` request.
commit_characters : None ,
2021-03-15 22:01:41 +00:00
.. original_item . clone ( )
2022-07-11 23:35:18 +00:00
} )
2021-03-15 22:01:41 +00:00
}
}
#[ derive(Debug, Deserialize, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
2020-12-08 10:36:13 +00:00
pub struct CompletionInfo {
entries : Vec < CompletionEntry > ,
2022-07-24 21:11:33 +00:00
// this is only used by Microsoft's telemetrics, which Deno doesn't use and
// there are issues with the value not matching the type definitions.
// flags: Option<CompletionInfoFlags>,
2021-03-15 22:01:41 +00:00
is_global_completion : bool ,
2020-12-08 10:36:13 +00:00
is_member_completion : bool ,
2021-03-15 22:01:41 +00:00
is_new_identifier_location : bool ,
metadata : Option < Value > ,
optional_replacement_span : Option < TextSpan > ,
2020-12-08 10:36:13 +00:00
}
impl CompletionInfo {
2021-03-15 22:01:41 +00:00
pub fn as_completion_response (
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2021-03-15 22:01:41 +00:00
settings : & config ::CompletionSettings ,
specifier : & ModuleSpecifier ,
position : u32 ,
2023-09-16 14:51:35 +00:00
language_server : & language_server ::Inner ,
2021-01-29 19:34:33 +00:00
) -> lsp ::CompletionResponse {
2020-12-08 10:36:13 +00:00
let items = self
. entries
2021-03-15 22:01:41 +00:00
. iter ( )
2024-02-21 02:45:00 +00:00
. flat_map ( | entry | {
2021-10-28 23:56:01 +00:00
entry . as_completion_item (
line_index . clone ( ) ,
self ,
settings ,
specifier ,
position ,
2023-09-16 14:51:35 +00:00
language_server ,
2021-10-28 23:56:01 +00:00
)
2021-03-15 22:01:41 +00:00
} )
2020-12-08 10:36:13 +00:00
. collect ( ) ;
2021-03-15 22:01:41 +00:00
let is_incomplete = self
. metadata
. clone ( )
. map ( | v | {
v . as_object ( )
. unwrap ( )
. get ( " isIncomplete " )
. unwrap_or ( & json! ( false ) )
. as_bool ( )
. unwrap ( )
} )
. unwrap_or ( false ) ;
lsp ::CompletionResponse ::List ( lsp ::CompletionList {
is_incomplete ,
items ,
} )
2020-12-08 10:36:13 +00:00
}
}
2021-03-15 22:01:41 +00:00
#[ derive(Debug, Deserialize, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CompletionItemData {
pub specifier : ModuleSpecifier ,
pub position : u32 ,
pub name : String ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub source : Option < String > ,
2023-09-18 23:59:26 +00:00
/// If present, the code action / text edit corresponding to this item should
/// be rewritten by replacing the first string with the second. Intended for
/// auto-import specifiers to be reverse-import-mapped.
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub specifier_rewrite : Option < ( String , String ) > ,
2021-03-15 22:01:41 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub data : Option < Value > ,
pub use_code_snippet : bool ,
}
2022-07-11 23:35:18 +00:00
#[ derive(Debug, Deserialize, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct CompletionEntryDataImport {
module_specifier : String ,
file_name : String ,
}
2021-03-15 22:01:41 +00:00
#[ derive(Debug, Default, Deserialize, Serialize) ]
2020-12-08 10:36:13 +00:00
#[ serde(rename_all = " camelCase " ) ]
pub struct CompletionEntry {
2021-03-15 22:01:41 +00:00
name : String ,
2020-12-08 10:36:13 +00:00
kind : ScriptElementKind ,
2021-03-15 22:01:41 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-12-08 10:36:13 +00:00
kind_modifiers : Option < String > ,
sort_text : String ,
2021-03-15 22:01:41 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-12-08 10:36:13 +00:00
insert_text : Option < String > ,
2021-03-15 22:01:41 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2022-06-01 00:19:18 +00:00
is_snippet : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-12-08 10:36:13 +00:00
replacement_span : Option < TextSpan > ,
2021-03-15 22:01:41 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-12-08 10:36:13 +00:00
has_action : Option < bool > ,
2021-03-15 22:01:41 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-12-08 10:36:13 +00:00
source : Option < String > ,
2021-03-15 22:01:41 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2022-06-01 00:19:18 +00:00
source_display : Option < Vec < SymbolDisplayPart > > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
label_details : Option < CompletionEntryLabelDetails > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-12-08 10:36:13 +00:00
is_recommended : Option < bool > ,
2021-03-15 22:01:41 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
is_from_unchecked_file : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2022-06-01 00:19:18 +00:00
is_package_json_import : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
is_import_statement_completion : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2021-03-15 22:01:41 +00:00
data : Option < Value > ,
2020-12-08 10:36:13 +00:00
}
impl CompletionEntry {
2021-03-15 22:01:41 +00:00
fn get_commit_characters (
& self ,
info : & CompletionInfo ,
settings : & config ::CompletionSettings ,
) -> Option < Vec < String > > {
if info . is_new_identifier_location {
return None ;
}
2020-12-08 10:36:13 +00:00
2021-03-15 22:01:41 +00:00
let mut commit_characters = vec! [ ] ;
match self . kind {
ScriptElementKind ::MemberGetAccessorElement
| ScriptElementKind ::MemberSetAccessorElement
| ScriptElementKind ::ConstructSignatureElement
| ScriptElementKind ::CallSignatureElement
| ScriptElementKind ::IndexSignatureElement
| ScriptElementKind ::EnumElement
| ScriptElementKind ::InterfaceElement = > {
commit_characters . push ( " . " ) ;
commit_characters . push ( " ; " ) ;
}
ScriptElementKind ::ModuleElement
| ScriptElementKind ::Alias
| ScriptElementKind ::ConstElement
| ScriptElementKind ::LetElement
| ScriptElementKind ::VariableElement
| ScriptElementKind ::LocalVariableElement
| ScriptElementKind ::MemberVariableElement
| ScriptElementKind ::ClassElement
| ScriptElementKind ::FunctionElement
| ScriptElementKind ::MemberFunctionElement
| ScriptElementKind ::Keyword
| ScriptElementKind ::ParameterElement = > {
commit_characters . push ( " . " ) ;
commit_characters . push ( " , " ) ;
commit_characters . push ( " ; " ) ;
if ! settings . complete_function_calls {
commit_characters . push ( " ( " ) ;
}
}
_ = > ( ) ,
2020-12-08 10:36:13 +00:00
}
2021-03-15 22:01:41 +00:00
if commit_characters . is_empty ( ) {
None
} else {
Some ( commit_characters . into_iter ( ) . map ( String ::from ) . collect ( ) )
}
}
fn get_filter_text ( & self ) -> Option < String > {
2021-09-16 02:07:52 +00:00
if self . name . starts_with ( '#' ) {
if let Some ( insert_text ) = & self . insert_text {
if insert_text . starts_with ( " this.# " ) {
return Some ( insert_text . replace ( " this.# " , " " ) ) ;
} else {
return Some ( insert_text . clone ( ) ) ;
}
} else {
2022-12-29 21:22:47 +00:00
return None ;
2021-09-16 02:07:52 +00:00
}
2021-03-15 22:01:41 +00:00
}
if let Some ( insert_text ) = & self . insert_text {
if insert_text . starts_with ( " this. " ) {
return None ;
}
if insert_text . starts_with ( '[' ) {
2021-09-16 02:07:52 +00:00
return Some (
BRACKET_ACCESSOR_RE
. replace ( insert_text , | caps : & Captures | format! ( " . {} " , & caps [ 1 ] ) )
. to_string ( ) ,
) ;
2020-12-08 10:36:13 +00:00
}
}
2021-03-15 22:01:41 +00:00
self . insert_text . clone ( )
}
2020-12-08 10:36:13 +00:00
2021-03-15 22:01:41 +00:00
pub fn as_completion_item (
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2021-03-15 22:01:41 +00:00
info : & CompletionInfo ,
settings : & config ::CompletionSettings ,
specifier : & ModuleSpecifier ,
position : u32 ,
2023-09-16 14:51:35 +00:00
language_server : & language_server ::Inner ,
2024-02-21 02:45:00 +00:00
) -> Option < lsp ::CompletionItem > {
2021-03-15 22:01:41 +00:00
let mut label = self . name . clone ( ) ;
2023-09-16 14:51:35 +00:00
let mut label_details : Option < lsp ::CompletionItemLabelDetails > = None ;
2021-03-15 22:01:41 +00:00
let mut kind : Option < lsp ::CompletionItemKind > =
Some ( self . kind . clone ( ) . into ( ) ) ;
2023-09-18 23:59:26 +00:00
let mut specifier_rewrite = None ;
2020-12-08 10:36:13 +00:00
2023-09-18 18:55:24 +00:00
let mut sort_text = if self . source . is_some ( ) {
format! ( " \u{ffff} {} " , self . sort_text )
2021-03-15 22:01:41 +00:00
} else {
2023-09-18 18:55:24 +00:00
self . sort_text . clone ( )
2021-03-15 22:01:41 +00:00
} ;
let preselect = self . is_recommended ;
let use_code_snippet = settings . complete_function_calls
2021-11-25 01:10:12 +00:00
& & ( kind = = Some ( lsp ::CompletionItemKind ::FUNCTION )
| | kind = = Some ( lsp ::CompletionItemKind ::METHOD ) ) ;
2022-07-11 23:35:18 +00:00
let commit_characters = self . get_commit_characters ( info , settings ) ;
2021-03-15 22:01:41 +00:00
let mut insert_text = self . insert_text . clone ( ) ;
2022-10-14 12:04:38 +00:00
let insert_text_format = match self . is_snippet {
Some ( true ) = > Some ( lsp ::InsertTextFormat ::SNIPPET ) ,
_ = > None ,
} ;
2021-03-15 22:01:41 +00:00
let range = self . replacement_span . clone ( ) ;
let mut filter_text = self . get_filter_text ( ) ;
let mut tags = None ;
let mut detail = None ;
if let Some ( kind_modifiers ) = & self . kind_modifiers {
let kind_modifiers = parse_kind_modifier ( kind_modifiers ) ;
if kind_modifiers . contains ( " optional " ) {
2020-12-08 10:36:13 +00:00
if insert_text . is_none ( ) {
2021-03-15 22:01:41 +00:00
insert_text = Some ( label . clone ( ) ) ;
2020-12-08 10:36:13 +00:00
}
2021-03-15 22:01:41 +00:00
if filter_text . is_none ( ) {
filter_text = Some ( label . clone ( ) ) ;
}
label + = " ? " ;
}
if kind_modifiers . contains ( " deprecated " ) {
2021-11-25 01:10:12 +00:00
tags = Some ( vec! [ lsp ::CompletionItemTag ::DEPRECATED ] ) ;
2021-03-15 22:01:41 +00:00
}
if kind_modifiers . contains ( " color " ) {
2021-11-25 01:10:12 +00:00
kind = Some ( lsp ::CompletionItemKind ::COLOR ) ;
2021-03-15 22:01:41 +00:00
}
if self . kind = = ScriptElementKind ::ScriptElement {
for ext_modifier in FILE_EXTENSION_KIND_MODIFIERS {
if kind_modifiers . contains ( ext_modifier ) {
detail = if self . name . to_lowercase ( ) . ends_with ( ext_modifier ) {
Some ( self . name . clone ( ) )
} else {
Some ( format! ( " {} {} " , self . name , ext_modifier ) )
} ;
break ;
}
2020-12-08 10:36:13 +00:00
}
}
}
2023-09-16 14:51:35 +00:00
if let Some ( source ) = & self . source {
2023-09-18 23:59:26 +00:00
let mut display_source = source . clone ( ) ;
2023-09-16 14:51:35 +00:00
if let Some ( data ) = & self . data {
if let Ok ( import_data ) =
serde_json ::from_value ::< CompletionEntryDataImport > ( data . clone ( ) )
{
2023-10-02 06:32:05 +00:00
if let Ok ( import_specifier ) = resolve_url ( & import_data . file_name ) {
2023-09-16 14:51:35 +00:00
if let Some ( new_module_specifier ) = language_server
2024-03-26 15:52:20 +00:00
. get_ts_response_import_mapper ( specifier )
2023-09-16 14:51:35 +00:00
. check_specifier ( & import_specifier , specifier )
. or_else ( | | relative_specifier ( specifier , & import_specifier ) )
{
2023-09-18 23:59:26 +00:00
display_source = new_module_specifier . clone ( ) ;
if new_module_specifier ! = import_data . module_specifier {
specifier_rewrite =
Some ( ( import_data . module_specifier , new_module_specifier ) ) ;
}
2024-02-21 02:45:00 +00:00
} else if source . starts_with ( jsr_url ( ) . as_str ( ) ) {
return None ;
2023-09-16 14:51:35 +00:00
}
}
}
}
2023-09-18 18:55:24 +00:00
// We want relative or bare (import-mapped or otherwise) specifiers to
// appear at the top.
2023-09-18 23:59:26 +00:00
if resolve_url ( & display_source ) . is_err ( ) {
2023-09-18 18:55:24 +00:00
sort_text + = " _0 " ;
} else {
sort_text + = " _1 " ;
}
2023-09-16 14:51:35 +00:00
label_details
. get_or_insert_with ( Default ::default )
2023-09-18 23:59:26 +00:00
. description = Some ( display_source ) ;
2023-09-16 14:51:35 +00:00
}
2021-03-15 22:01:41 +00:00
let text_edit =
2021-04-01 09:18:51 +00:00
if let ( Some ( text_span ) , Some ( new_text ) ) = ( range , & insert_text ) {
2021-03-15 22:01:41 +00:00
let range = text_span . to_range ( line_index ) ;
let insert_replace_edit = lsp ::InsertReplaceEdit {
2021-04-01 09:18:51 +00:00
new_text : new_text . clone ( ) ,
2021-03-15 22:01:41 +00:00
insert : range ,
replace : range ,
} ;
Some ( insert_replace_edit . into ( ) )
2020-12-08 10:36:13 +00:00
} else {
2021-03-15 22:01:41 +00:00
None
} ;
2021-03-25 00:13:37 +00:00
let tsc = CompletionItemData {
2021-03-15 22:01:41 +00:00
specifier : specifier . clone ( ) ,
position ,
name : self . name . clone ( ) ,
source : self . source . clone ( ) ,
2023-09-18 23:59:26 +00:00
specifier_rewrite ,
2021-03-15 22:01:41 +00:00
data : self . data . clone ( ) ,
use_code_snippet ,
} ;
2020-12-08 10:36:13 +00:00
2024-02-21 02:45:00 +00:00
Some ( lsp ::CompletionItem {
2021-03-15 22:01:41 +00:00
label ,
2023-09-16 14:51:35 +00:00
label_details ,
2021-03-15 22:01:41 +00:00
kind ,
2023-09-18 18:55:24 +00:00
sort_text : Some ( sort_text ) ,
2021-03-15 22:01:41 +00:00
preselect ,
text_edit ,
filter_text ,
2021-04-01 09:18:51 +00:00
insert_text ,
2022-10-14 12:04:38 +00:00
insert_text_format ,
2021-03-15 22:01:41 +00:00
detail ,
tags ,
2022-07-11 23:35:18 +00:00
commit_characters ,
data : Some ( json! ( { " tsc " : tsc } ) ) ,
2021-03-15 22:01:41 +00:00
.. Default ::default ( )
2024-02-21 02:45:00 +00:00
} )
2020-12-08 10:36:13 +00:00
}
}
2022-06-01 00:19:18 +00:00
#[ derive(Debug, Default, Deserialize, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct CompletionEntryLabelDetails {
#[ serde(skip_serializing_if = " Option::is_none " ) ]
detail : Option < String > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
description : Option < String > ,
}
2021-04-02 06:21:07 +00:00
#[ derive(Debug, Deserialize) ]
pub enum OutliningSpanKind {
#[ serde(rename = " comment " ) ]
Comment ,
#[ serde(rename = " region " ) ]
Region ,
#[ serde(rename = " code " ) ]
Code ,
#[ serde(rename = " imports " ) ]
Imports ,
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct OutliningSpan {
text_span : TextSpan ,
2022-02-23 21:01:20 +00:00
// hint_span: TextSpan,
// banner_text: String,
// auto_collapse: bool,
2021-04-02 06:21:07 +00:00
kind : OutliningSpanKind ,
}
const FOLD_END_PAIR_CHARACTERS : & [ u8 ] = & [ b '}' , b ']' , b ')' , b '`' ] ;
impl OutliningSpan {
pub fn to_folding_range (
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2021-04-02 06:21:07 +00:00
content : & [ u8 ] ,
line_folding_only : bool ,
) -> lsp ::FoldingRange {
2021-10-28 23:56:01 +00:00
let range = self . text_span . to_range ( line_index . clone ( ) ) ;
2021-04-02 06:21:07 +00:00
lsp ::FoldingRange {
start_line : range . start . line ,
start_character : if line_folding_only {
None
} else {
Some ( range . start . character )
} ,
end_line : self . adjust_folding_end_line (
& range ,
line_index ,
content ,
line_folding_only ,
) ,
end_character : if line_folding_only {
None
} else {
Some ( range . end . character )
} ,
kind : self . get_folding_range_kind ( & self . kind ) ,
2023-09-26 20:57:14 +00:00
collapsed_text : None ,
2021-04-02 06:21:07 +00:00
}
}
fn adjust_folding_end_line (
& self ,
range : & lsp ::Range ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2021-04-02 06:21:07 +00:00
content : & [ u8 ] ,
line_folding_only : bool ,
) -> u32 {
2021-04-06 03:27:27 +00:00
if line_folding_only & & range . end . line > 0 & & range . end . character > 0 {
2021-04-02 06:21:07 +00:00
let offset_end : usize = line_index . offset ( range . end ) . unwrap ( ) . into ( ) ;
let fold_end_char = content [ offset_end - 1 ] ;
if FOLD_END_PAIR_CHARACTERS . contains ( & fold_end_char ) {
return cmp ::max ( range . end . line - 1 , range . start . line ) ;
}
}
range . end . line
}
fn get_folding_range_kind (
& self ,
span_kind : & OutliningSpanKind ,
) -> Option < lsp ::FoldingRangeKind > {
match span_kind {
OutliningSpanKind ::Comment = > Some ( lsp ::FoldingRangeKind ::Comment ) ,
OutliningSpanKind ::Region = > Some ( lsp ::FoldingRangeKind ::Region ) ,
OutliningSpanKind ::Imports = > Some ( lsp ::FoldingRangeKind ::Imports ) ,
_ = > None ,
}
}
}
2021-02-16 02:34:09 +00:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct SignatureHelpItems {
items : Vec < SignatureHelpItem > ,
2022-02-23 21:01:20 +00:00
// applicable_span: TextSpan,
2021-02-16 02:34:09 +00:00
selected_item_index : u32 ,
argument_index : u32 ,
2022-02-23 21:01:20 +00:00
// argument_count: u32,
2021-02-16 02:34:09 +00:00
}
impl SignatureHelpItems {
2022-03-23 13:54:22 +00:00
pub fn into_signature_help (
2022-02-09 23:08:53 +00:00
self ,
language_server : & language_server ::Inner ,
) -> lsp ::SignatureHelp {
2021-02-16 02:34:09 +00:00
lsp ::SignatureHelp {
signatures : self
. items
. into_iter ( )
2022-02-09 23:08:53 +00:00
. map ( | item | item . into_signature_information ( language_server ) )
2021-02-16 02:34:09 +00:00
. collect ( ) ,
active_parameter : Some ( self . argument_index ) ,
active_signature : Some ( self . selected_item_index ) ,
}
}
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct SignatureHelpItem {
2022-02-23 21:01:20 +00:00
// is_variadic: bool,
2021-02-16 02:34:09 +00:00
prefix_display_parts : Vec < SymbolDisplayPart > ,
suffix_display_parts : Vec < SymbolDisplayPart > ,
2022-02-23 21:01:20 +00:00
// separator_display_parts: Vec<SymbolDisplayPart>,
2021-02-16 02:34:09 +00:00
parameters : Vec < SignatureHelpParameter > ,
documentation : Vec < SymbolDisplayPart > ,
2022-02-23 21:01:20 +00:00
// tags: Vec<JsDocTagInfo>,
2021-02-16 02:34:09 +00:00
}
impl SignatureHelpItem {
2022-03-23 13:54:22 +00:00
pub fn into_signature_information (
2022-02-09 23:08:53 +00:00
self ,
language_server : & language_server ::Inner ,
) -> lsp ::SignatureInformation {
let prefix_text =
display_parts_to_string ( & self . prefix_display_parts , language_server ) ;
2021-02-16 02:34:09 +00:00
let params_text = self
. parameters
. iter ( )
2022-02-09 23:08:53 +00:00
. map ( | param | {
display_parts_to_string ( & param . display_parts , language_server )
} )
2021-02-16 02:34:09 +00:00
. collect ::< Vec < String > > ( )
. join ( " , " ) ;
2022-02-09 23:08:53 +00:00
let suffix_text =
display_parts_to_string ( & self . suffix_display_parts , language_server ) ;
let documentation =
display_parts_to_string ( & self . documentation , language_server ) ;
2021-02-16 02:34:09 +00:00
lsp ::SignatureInformation {
2023-01-27 15:43:16 +00:00
label : format ! ( " {prefix_text}{params_text}{suffix_text} " ) ,
2021-11-07 22:26:11 +00:00
documentation : Some ( lsp ::Documentation ::MarkupContent (
lsp ::MarkupContent {
kind : lsp ::MarkupKind ::Markdown ,
value : documentation ,
} ,
) ) ,
2021-02-16 02:34:09 +00:00
parameters : Some (
self
. parameters
. into_iter ( )
2022-02-09 23:08:53 +00:00
. map ( | param | param . into_parameter_information ( language_server ) )
2021-02-16 02:34:09 +00:00
. collect ( ) ,
) ,
active_parameter : None ,
}
}
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct SignatureHelpParameter {
2022-02-23 21:01:20 +00:00
// name: String,
2021-02-16 02:34:09 +00:00
documentation : Vec < SymbolDisplayPart > ,
display_parts : Vec < SymbolDisplayPart > ,
2022-02-23 21:01:20 +00:00
// is_optional: bool,
2021-02-16 02:34:09 +00:00
}
impl SignatureHelpParameter {
2022-03-23 13:54:22 +00:00
pub fn into_parameter_information (
2022-02-09 23:08:53 +00:00
self ,
language_server : & language_server ::Inner ,
) -> lsp ::ParameterInformation {
let documentation =
display_parts_to_string ( & self . documentation , language_server ) ;
2021-02-16 02:34:09 +00:00
lsp ::ParameterInformation {
label : lsp ::ParameterLabel ::Simple ( display_parts_to_string (
2021-03-15 22:01:41 +00:00
& self . display_parts ,
2022-02-09 23:08:53 +00:00
language_server ,
2021-02-16 02:34:09 +00:00
) ) ,
2021-11-07 22:26:11 +00:00
documentation : Some ( lsp ::Documentation ::MarkupContent (
lsp ::MarkupContent {
kind : lsp ::MarkupKind ::Markdown ,
value : documentation ,
} ,
) ) ,
2021-02-16 02:34:09 +00:00
}
}
}
2021-03-23 23:33:25 +00:00
#[ derive(Debug, Clone, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct SelectionRange {
text_span : TextSpan ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
parent : Option < Box < SelectionRange > > ,
}
impl SelectionRange {
pub fn to_selection_range (
& self ,
2021-10-28 23:56:01 +00:00
line_index : Arc < LineIndex > ,
2021-03-23 23:33:25 +00:00
) -> lsp ::SelectionRange {
lsp ::SelectionRange {
2021-10-28 23:56:01 +00:00
range : self . text_span . to_range ( line_index . clone ( ) ) ,
2021-03-25 18:17:37 +00:00
parent : self . parent . as_ref ( ) . map ( | parent_selection | {
Box ::new ( parent_selection . to_selection_range ( line_index ) )
} ) ,
2021-03-23 23:33:25 +00:00
}
}
}
2023-09-29 19:44:59 +00:00
#[ derive(Debug, Default) ]
pub struct TscSpecifierMap {
normalized_specifiers : DashMap < String , ModuleSpecifier > ,
denormalized_specifiers : DashMap < ModuleSpecifier , String > ,
2020-12-07 10:46:39 +00:00
}
2023-09-29 19:44:59 +00:00
impl TscSpecifierMap {
2023-10-02 06:32:05 +00:00
pub fn new ( ) -> Self {
Self ::default ( )
}
2023-09-19 15:37:27 +00:00
/// Convert the specifier to one compatible with tsc. Cache the resulting
/// mapping in case it needs to be reversed.
2023-09-29 19:44:59 +00:00
// TODO(nayeemrmn): Factor in out-of-band media type here.
pub fn denormalize ( & self , specifier : & ModuleSpecifier ) -> String {
2023-09-19 15:37:27 +00:00
let original = specifier ;
if let Some ( specifier ) = self . denormalized_specifiers . get ( original ) {
return specifier . to_string ( ) ;
}
2023-11-24 22:35:33 +00:00
let mut specifier = original . to_string ( ) ;
let media_type = MediaType ::from_specifier ( original ) ;
2023-09-19 15:37:27 +00:00
// If the URL-inferred media type doesn't correspond to tsc's path-inferred
// media type, force it to be the same by appending an extension.
if MediaType ::from_path ( Path ::new ( specifier . as_str ( ) ) ) ! = media_type {
specifier + = media_type . as_ts_extension ( ) ;
}
if specifier ! = original . as_str ( ) {
self
. normalized_specifiers
. insert ( specifier . clone ( ) , original . clone ( ) ) ;
}
specifier
2021-06-21 21:18:32 +00:00
}
2023-09-19 15:37:27 +00:00
/// Convert the specifier from one compatible with tsc. Cache the resulting
/// mapping in case it needs to be reversed.
2023-09-29 19:44:59 +00:00
pub fn normalize < S : AsRef < str > > (
& self ,
2021-06-21 21:18:32 +00:00
specifier : S ,
) -> Result < ModuleSpecifier , AnyError > {
2023-09-19 15:37:27 +00:00
let original = specifier . as_ref ( ) ;
if let Some ( specifier ) = self . normalized_specifiers . get ( original ) {
return Ok ( specifier . clone ( ) ) ;
}
let specifier_str = original . replace ( " .d.ts.d.ts " , " .d.ts " ) ;
let specifier = match ModuleSpecifier ::parse ( & specifier_str ) {
Ok ( s ) = > s ,
Err ( err ) = > return Err ( err . into ( ) ) ,
} ;
if specifier . as_str ( ) ! = original {
2021-06-21 21:18:32 +00:00
self
2023-09-19 15:37:27 +00:00
. denormalized_specifiers
. insert ( specifier . clone ( ) , original . to_string ( ) ) ;
2021-06-21 21:18:32 +00:00
}
2023-09-19 15:37:27 +00:00
Ok ( specifier )
2021-06-21 21:18:32 +00:00
}
2023-09-29 19:44:59 +00:00
}
2023-12-11 15:59:09 +00:00
// TODO(bartlomieju): we have similar struct in `cli/tsc/mod.rs` - maybe at least change
// the name of the struct to avoid confusion?
2023-09-29 19:44:59 +00:00
struct State {
last_id : usize ,
performance : Arc < Performance > ,
2024-04-12 23:04:54 +00:00
// the response from JS, as a JSON string
response : Option < String > ,
2023-09-29 19:44:59 +00:00
state_snapshot : Arc < StateSnapshot > ,
specifier_map : Arc < TscSpecifierMap > ,
token : CancellationToken ,
}
impl State {
fn new (
state_snapshot : Arc < StateSnapshot > ,
specifier_map : Arc < TscSpecifierMap > ,
performance : Arc < Performance > ,
) -> Self {
Self {
last_id : 1 ,
performance ,
response : None ,
state_snapshot ,
specifier_map ,
token : Default ::default ( ) ,
}
}
2020-12-07 10:46:39 +00:00
2022-05-15 18:41:37 +00:00
fn get_asset_or_document (
& self ,
specifier : & ModuleSpecifier ,
) -> Option < AssetOrDocument > {
let snapshot = & self . state_snapshot ;
if specifier . scheme ( ) = = " asset " {
snapshot . assets . get ( specifier ) . map ( AssetOrDocument ::Asset )
} else {
snapshot
. documents
. get ( specifier )
. map ( AssetOrDocument ::Document )
}
}
fn script_version ( & self , specifier : & ModuleSpecifier ) -> Option < String > {
if specifier . scheme ( ) = = " asset " {
if self . state_snapshot . assets . contains_key ( specifier ) {
Some ( " 1 " . to_string ( ) )
} else {
None
}
} else {
self
. state_snapshot
. documents
. get ( specifier )
. map ( | d | d . script_version ( ) )
}
2020-12-07 10:46:39 +00:00
}
}
2023-09-12 11:14:45 +00:00
#[ op2(fast) ]
2022-05-13 08:36:31 +00:00
fn op_is_cancelled ( state : & mut OpState ) -> bool {
2022-03-14 17:44:15 +00:00
let state = state . borrow_mut ::< State > ( ) ;
2022-05-13 08:36:31 +00:00
state . token . is_cancelled ( )
2022-02-02 14:25:22 +00:00
}
2023-09-12 11:14:45 +00:00
#[ op2(fast) ]
fn op_is_node_file ( state : & mut OpState , #[ string ] path : String ) -> bool {
2022-10-21 15:20:18 +00:00
let state = state . borrow ::< State > ( ) ;
2023-12-11 16:33:56 +00:00
let mark = state . performance . mark ( " tsc.op.op_is_node_file " ) ;
let r = match ModuleSpecifier ::parse ( & path ) {
2024-04-26 20:39:33 +00:00
Ok ( specifier ) = > state . state_snapshot . resolver . in_npm_package ( & specifier ) ,
2022-10-21 15:20:18 +00:00
Err ( _ ) = > false ,
2023-12-11 16:33:56 +00:00
} ;
state . performance . measure ( mark ) ;
r
2022-10-21 15:20:18 +00:00
}
2023-09-12 00:55:57 +00:00
#[ derive(Debug, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct LoadResponse {
data : Arc < str > ,
script_kind : i32 ,
version : Option < String > ,
2024-04-15 21:50:52 +00:00
is_cjs : bool ,
2023-09-12 00:55:57 +00:00
}
2023-09-12 11:14:45 +00:00
#[ op2 ]
2023-12-01 21:57:52 +00:00
fn op_load < ' s > (
scope : & ' s mut v8 ::HandleScope ,
2022-03-14 17:44:15 +00:00
state : & mut OpState ,
2023-12-03 02:07:04 +00:00
#[ string ] specifier : & str ,
2023-12-01 21:57:52 +00:00
) -> Result < v8 ::Local < ' s , v8 ::Value > , AnyError > {
2022-03-14 17:44:15 +00:00
let state = state . borrow_mut ::< State > ( ) ;
2023-12-03 02:07:04 +00:00
let mark = state
. performance
. mark_with_args ( " tsc.op.op_load " , specifier ) ;
let specifier = state . specifier_map . normalize ( specifier ) ? ;
2023-12-01 21:57:52 +00:00
let maybe_load_response =
2024-04-11 20:55:27 +00:00
if specifier . as_str ( ) = = MISSING_DEPENDENCY_SPECIFIER {
2023-12-12 10:26:27 +00:00
None
2023-12-01 21:57:52 +00:00
} else {
let asset_or_document = state . get_asset_or_document ( & specifier ) ;
asset_or_document . map ( | doc | LoadResponse {
data : doc . text ( ) ,
script_kind : crate ::tsc ::as_ts_script_kind ( doc . media_type ( ) ) ,
version : state . script_version ( & specifier ) ,
2024-04-15 21:50:52 +00:00
is_cjs : matches ! (
doc . media_type ( ) ,
MediaType ::Cjs | MediaType ::Cts | MediaType ::Dcts
) ,
2023-12-01 21:57:52 +00:00
} )
} ;
let serialized = serde_v8 ::to_v8 ( scope , maybe_load_response ) ? ;
2022-01-17 22:09:43 +00:00
state . performance . measure ( mark ) ;
2023-12-01 21:57:52 +00:00
Ok ( serialized )
2021-06-21 21:18:32 +00:00
}
2024-04-17 20:40:42 +00:00
#[ op2(fast) ]
fn op_release (
state : & mut OpState ,
#[ string ] specifier : & str ,
) -> Result < ( ) , AnyError > {
let state = state . borrow_mut ::< State > ( ) ;
let mark = state
. performance
. mark_with_args ( " tsc.op.op_release " , specifier ) ;
let specifier = state . specifier_map . normalize ( specifier ) ? ;
state . state_snapshot . documents . release ( & specifier ) ;
state . performance . measure ( mark ) ;
Ok ( ( ) )
}
2023-09-12 11:14:45 +00:00
#[ op2 ]
2024-04-11 20:55:27 +00:00
#[ serde ]
fn op_resolve (
2022-03-14 17:44:15 +00:00
state : & mut OpState ,
2024-04-19 17:11:17 +00:00
#[ string ] base : String ,
#[ serde ] specifiers : Vec < String > ,
2024-04-12 19:45:38 +00:00
) -> Result < Vec < Option < ( String , String ) > > , AnyError > {
2024-04-19 17:11:17 +00:00
op_resolve_inner ( state , ResolveArgs { base , specifiers } )
2024-04-12 19:45:38 +00:00
}
#[ inline ]
fn op_resolve_inner (
state : & mut OpState ,
args : ResolveArgs ,
2024-04-11 20:55:27 +00:00
) -> Result < Vec < Option < ( String , String ) > > , AnyError > {
2022-03-14 17:44:15 +00:00
let state = state . borrow_mut ::< State > ( ) ;
2023-12-01 02:54:59 +00:00
let mark = state . performance . mark_with_args ( " tsc.op.op_resolve " , & args ) ;
2023-09-29 19:44:59 +00:00
let referrer = state . specifier_map . normalize ( & args . base ) ? ;
2024-04-14 21:42:58 +00:00
let specifiers = state
. state_snapshot
. documents
2024-04-26 20:39:33 +00:00
. resolve ( & args . specifiers , & referrer )
2024-04-14 21:42:58 +00:00
. into_iter ( )
. map ( | o | {
o . map ( | ( s , mt ) | {
(
state . specifier_map . denormalize ( & s ) ,
mt . as_ts_extension ( ) . to_string ( ) ,
)
} )
} )
. collect ( ) ;
2020-12-07 10:46:39 +00:00
2022-01-17 22:09:43 +00:00
state . performance . measure ( mark ) ;
2024-04-11 20:55:27 +00:00
Ok ( specifiers )
2020-12-07 10:46:39 +00:00
}
2024-04-12 23:04:54 +00:00
#[ op2(fast) ]
fn op_respond ( state : & mut OpState , #[ string ] response : String ) {
2022-03-14 17:44:15 +00:00
let state = state . borrow_mut ::< State > ( ) ;
2024-04-12 23:04:54 +00:00
state . response = Some ( response ) ;
2020-12-07 10:46:39 +00:00
}
2023-09-12 11:14:45 +00:00
#[ op2 ]
#[ serde ]
2023-01-24 14:05:54 +00:00
fn op_script_names ( state : & mut OpState ) -> Vec < String > {
2022-03-14 17:44:15 +00:00
let state = state . borrow_mut ::< State > ( ) ;
2023-12-11 16:33:56 +00:00
let mark = state . performance . mark ( " tsc.op.op_script_names " ) ;
2023-01-24 14:05:54 +00:00
let documents = & state . state_snapshot . documents ;
2023-03-30 21:47:53 +00:00
let all_docs = documents . documents ( DocumentsFilter ::AllDiagnosable ) ;
2023-03-11 16:43:45 +00:00
let mut seen = HashSet ::new ( ) ;
2023-03-30 21:47:53 +00:00
let mut result = Vec ::new ( ) ;
2023-01-24 14:05:54 +00:00
if documents . has_injected_types_node_package ( ) {
// ensure this is first so it resolves the node types first
2023-03-11 16:43:45 +00:00
let specifier = " asset:///node_types.d.ts " ;
result . push ( specifier . to_string ( ) ) ;
seen . insert ( specifier ) ;
}
// inject these next because they're global
2024-05-01 05:54:25 +00:00
for import in documents . module_graph_imports ( ) {
if seen . insert ( import . as_str ( ) ) {
result . push ( import . to_string ( ) ) ;
2023-03-11 16:43:45 +00:00
}
}
// finally include the documents and all their dependencies
2023-03-30 21:47:53 +00:00
for doc in & all_docs {
let specifiers = std ::iter ::once ( doc . specifier ( ) ) . chain (
doc
. dependencies ( )
. values ( )
. filter_map ( | dep | dep . get_type ( ) . or_else ( | | dep . get_code ( ) ) ) ,
) ;
for specifier in specifiers {
2023-08-02 20:57:25 +00:00
if seen . insert ( specifier . as_str ( ) ) {
2023-12-07 00:03:18 +00:00
if let Some ( specifier ) = documents . resolve_specifier ( specifier ) {
2023-08-02 20:57:25 +00:00
// only include dependencies we know to exist otherwise typescript will error
2024-04-08 18:45:20 +00:00
if documents . exists ( & specifier )
& & ( specifier . scheme ( ) = = " file " | | documents . is_open ( & specifier ) )
{
2023-08-02 20:57:25 +00:00
result . push ( specifier . to_string ( ) ) ;
}
}
2023-03-11 16:43:45 +00:00
}
}
2023-01-24 14:05:54 +00:00
}
2023-12-11 16:33:56 +00:00
let r = result
2023-09-29 19:44:59 +00:00
. into_iter ( )
. map ( | s | match ModuleSpecifier ::parse ( & s ) {
Ok ( s ) = > state . specifier_map . denormalize ( & s ) ,
Err ( _ ) = > s ,
} )
2023-12-11 16:33:56 +00:00
. collect ( ) ;
state . performance . measure ( mark ) ;
r
2020-12-07 10:46:39 +00:00
}
2023-09-12 11:14:45 +00:00
#[ op2 ]
#[ string ]
2021-05-02 12:29:19 +00:00
fn op_script_version (
2022-03-14 17:44:15 +00:00
state : & mut OpState ,
2023-12-01 21:57:52 +00:00
#[ string ] specifier : & str ,
2021-02-25 03:15:55 +00:00
) -> Result < Option < String > , AnyError > {
2022-03-14 17:44:15 +00:00
let state = state . borrow_mut ::< State > ( ) ;
2023-12-11 16:33:56 +00:00
let mark = state . performance . mark ( " tsc.op.op_script_version " ) ;
2023-12-01 21:57:52 +00:00
let specifier = state . specifier_map . normalize ( specifier ) ? ;
2023-12-11 16:33:56 +00:00
let r = state . script_version ( & specifier ) ;
state . performance . measure ( mark ) ;
Ok ( r )
2020-12-07 10:46:39 +00:00
}
2024-03-26 15:52:20 +00:00
#[ op2 ]
#[ serde ]
fn op_ts_config ( state : & mut OpState ) -> serde_json ::Value {
let state = state . borrow_mut ::< State > ( ) ;
let mark = state . performance . mark ( " tsc.op.op_ts_config " ) ;
2024-04-02 22:02:50 +00:00
let r = json! ( state . state_snapshot . config . tree . root_ts_config ( ) ) ;
2024-03-26 15:52:20 +00:00
state . performance . measure ( mark ) ;
r
}
2024-04-15 00:07:04 +00:00
#[ op2(fast) ]
#[ number ]
fn op_project_version ( state : & mut OpState ) -> usize {
2024-03-26 15:52:20 +00:00
let state : & mut State = state . borrow_mut ::< State > ( ) ;
2023-12-28 00:13:57 +00:00
let mark = state . performance . mark ( " tsc.op.op_project_version " ) ;
2024-04-15 00:07:04 +00:00
let r = state . state_snapshot . project_version ;
2023-12-28 00:13:57 +00:00
state . performance . measure ( mark ) ;
r
}
2024-04-23 15:50:30 +00:00
struct TscRuntime {
js_runtime : JsRuntime ,
server_request_fn_global : v8 ::Global < v8 ::Function > ,
}
impl TscRuntime {
fn new ( mut js_runtime : JsRuntime ) -> Self {
let server_request_fn_global = {
let context = js_runtime . main_context ( ) ;
let scope = & mut js_runtime . handle_scope ( ) ;
let context_local = v8 ::Local ::new ( scope , context ) ;
let global_obj = context_local . global ( scope ) ;
let server_request_fn_str =
v8 ::String ::new_external_onebyte_static ( scope , b " serverRequest " )
. unwrap ( ) ;
let server_request_fn = v8 ::Local ::try_from (
global_obj . get ( scope , server_request_fn_str . into ( ) ) . unwrap ( ) ,
)
. unwrap ( ) ;
v8 ::Global ::new ( scope , server_request_fn )
} ;
Self {
server_request_fn_global ,
js_runtime ,
}
}
/// Send a request into the runtime and return the JSON string containing the response.
fn request (
& mut self ,
state_snapshot : Arc < StateSnapshot > ,
request : TscRequest ,
change : Option < PendingChange > ,
token : CancellationToken ,
) -> Result < String , AnyError > {
if token . is_cancelled ( ) {
return Err ( anyhow! ( " Operation was cancelled. " ) ) ;
}
let ( performance , id ) = {
let op_state = self . js_runtime . op_state ( ) ;
let mut op_state = op_state . borrow_mut ( ) ;
let state = op_state . borrow_mut ::< State > ( ) ;
state . state_snapshot = state_snapshot ;
state . token = token ;
state . last_id + = 1 ;
let id = state . last_id ;
( state . performance . clone ( ) , id )
} ;
let mark = performance
. mark_with_args ( format! ( " tsc.host. {} " , request . method ( ) ) , & request ) ;
{
let scope = & mut self . js_runtime . handle_scope ( ) ;
let tc_scope = & mut v8 ::TryCatch ::new ( scope ) ;
let server_request_fn =
v8 ::Local ::new ( tc_scope , & self . server_request_fn_global ) ;
let undefined = v8 ::undefined ( tc_scope ) . into ( ) ;
let change = if let Some ( change ) = change {
change . to_v8 ( tc_scope ) ?
} else {
v8 ::null ( tc_scope ) . into ( )
} ;
let ( method , req_args ) = request . to_server_request ( tc_scope ) ? ;
let args = vec! [
v8 ::Integer ::new ( tc_scope , id as i32 ) . into ( ) ,
v8 ::String ::new ( tc_scope , method ) . unwrap ( ) . into ( ) ,
req_args . unwrap_or_else ( | | v8 ::Array ::new ( tc_scope , 0 ) . into ( ) ) ,
change ,
] ;
server_request_fn . call ( tc_scope , undefined , & args ) ;
if tc_scope . has_caught ( ) & & ! tc_scope . has_terminated ( ) {
2024-04-24 19:46:08 +00:00
if let Some ( stack_trace ) = tc_scope . stack_trace ( ) {
lsp_warn! (
" Error during TS request \" {method} \" : \n {} " ,
stack_trace . to_rust_string_lossy ( tc_scope ) ,
) ;
} else {
lsp_warn! (
" Error during TS request \" {method} \" : \n {} " ,
tc_scope
. exception ( )
. map ( | exc | exc . to_rust_string_lossy ( tc_scope ) )
. unwrap_or_default ( ) ,
) ;
}
2024-04-23 15:50:30 +00:00
tc_scope . rethrow ( ) ;
}
}
let op_state = self . js_runtime . op_state ( ) ;
let mut op_state = op_state . borrow_mut ( ) ;
let state = op_state . borrow_mut ::< State > ( ) ;
performance . measure ( mark ) ;
state . response . take ( ) . ok_or_else ( | | {
custom_error (
" RequestError " ,
" The response was not received for the request. " ,
)
} )
}
}
2023-12-22 01:04:02 +00:00
fn run_tsc_thread (
mut request_rx : UnboundedReceiver < Request > ,
2023-08-02 00:49:09 +00:00
performance : Arc < Performance > ,
cache : Arc < dyn HttpCache > ,
2023-09-29 19:44:59 +00:00
specifier_map : Arc < TscSpecifierMap > ,
2023-12-22 01:04:02 +00:00
maybe_inspector_server : Option < Arc < InspectorServer > > ,
) {
let has_inspector_server = maybe_inspector_server . is_some ( ) ;
// Create and setup a JsRuntime based on a snapshot. It is expected that the
// supplied snapshot is an isolate that contains the TypeScript language
// server.
let mut tsc_runtime = JsRuntime ::new ( RuntimeOptions {
2024-04-02 22:02:50 +00:00
extensions : vec ! [ deno_tsc ::init_ops ( performance , cache , specifier_map ) ] ,
2022-07-04 22:12:41 +00:00
startup_snapshot : Some ( tsc ::compiler_snapshot ( ) ) ,
2023-12-22 01:04:02 +00:00
inspector : maybe_inspector_server . is_some ( ) ,
2020-12-07 10:46:39 +00:00
.. Default ::default ( )
2023-12-22 01:04:02 +00:00
} ) ;
if let Some ( server ) = maybe_inspector_server {
server . register_inspector (
" ext:deno_tsc/99_main_compiler.js " . to_string ( ) ,
& mut tsc_runtime ,
false ,
) ;
}
let tsc_future = async {
start_tsc ( & mut tsc_runtime , false ) . unwrap ( ) ;
let ( request_signal_tx , mut request_signal_rx ) = mpsc ::unbounded_channel ::< ( ) > ( ) ;
2024-04-23 15:50:30 +00:00
let tsc_runtime = Rc ::new ( tokio ::sync ::Mutex ::new ( TscRuntime ::new ( tsc_runtime ) ) ) ;
2023-12-22 01:04:02 +00:00
let tsc_runtime_ = tsc_runtime . clone ( ) ;
let event_loop_fut = async {
loop {
if has_inspector_server {
2024-04-23 15:50:30 +00:00
tsc_runtime_ . lock ( ) . await . js_runtime . run_event_loop ( PollEventLoopOptions {
2023-12-22 01:04:02 +00:00
wait_for_inspector : false ,
pump_v8_message_loop : true ,
} ) . await . ok ( ) ;
}
request_signal_rx . recv_many ( & mut vec! [ ] , 1000 ) . await ;
}
} ;
tokio ::pin! ( event_loop_fut ) ;
loop {
tokio ::select! {
biased ;
( maybe_request , mut tsc_runtime ) = async { ( request_rx . recv ( ) . await , tsc_runtime . lock ( ) . await ) } = > {
2024-04-22 15:03:16 +00:00
if let Some ( ( req , state_snapshot , tx , token , pending_change ) ) = maybe_request {
2024-04-23 15:50:30 +00:00
let value = tsc_runtime . request ( state_snapshot , req , pending_change , token . clone ( ) ) ;
2023-12-22 01:04:02 +00:00
request_signal_tx . send ( ( ) ) . unwrap ( ) ;
let was_sent = tx . send ( value ) . is_ok ( ) ;
// Don't print the send error if the token is cancelled, it's expected
// to fail in that case and this commonly occurs.
if ! was_sent & & ! token . is_cancelled ( ) {
lsp_warn! ( " Unable to send result to client. " ) ;
}
} else {
break ;
}
} ,
_ = & mut event_loop_fut = > { }
}
}
}
. boxed_local ( ) ;
let runtime = create_basic_runtime ( ) ;
runtime . block_on ( tsc_future )
2021-12-29 14:22:00 +00:00
}
2020-12-07 10:46:39 +00:00
2023-03-17 18:22:15 +00:00
deno_core ::extension! ( deno_tsc ,
ops = [
op_is_cancelled ,
op_is_node_file ,
op_load ,
2024-04-17 20:40:42 +00:00
op_release ,
2023-03-17 18:22:15 +00:00
op_resolve ,
op_respond ,
op_script_names ,
op_script_version ,
2024-03-26 15:52:20 +00:00
op_ts_config ,
2023-12-28 00:13:57 +00:00
op_project_version ,
2023-03-17 18:22:15 +00:00
] ,
2023-03-17 22:15:27 +00:00
options = {
2023-07-08 20:06:45 +00:00
performance : Arc < Performance > ,
2023-08-02 00:49:09 +00:00
cache : Arc < dyn HttpCache > ,
2023-09-29 19:44:59 +00:00
specifier_map : Arc < TscSpecifierMap > ,
2023-03-17 18:22:15 +00:00
} ,
2023-03-17 22:15:27 +00:00
state = | state , options | {
2023-03-17 18:22:15 +00:00
state . put ( State ::new (
2023-07-08 20:06:45 +00:00
Arc ::new ( StateSnapshot {
2024-04-15 00:07:04 +00:00
project_version : 0 ,
2023-07-08 20:06:45 +00:00
assets : Default ::default ( ) ,
cache_metadata : CacheMetadata ::new ( options . cache . clone ( ) ) ,
2023-09-09 18:37:01 +00:00
config : Default ::default ( ) ,
2023-07-08 20:06:45 +00:00
documents : Documents ::new ( options . cache . clone ( ) ) ,
2024-04-26 20:39:33 +00:00
resolver : Default ::default ( ) ,
2023-07-08 20:06:45 +00:00
} ) ,
2023-09-29 19:44:59 +00:00
options . specifier_map ,
2023-03-17 22:15:27 +00:00
options . performance ,
2023-03-17 18:22:15 +00:00
) ) ;
} ,
) ;
2021-06-21 21:18:32 +00:00
/// Instruct a language server runtime to start the language server and provide
/// it with a minimal bootstrap configuration.
2023-12-11 15:59:09 +00:00
fn start_tsc ( runtime : & mut JsRuntime , debug : bool ) -> Result < ( ) , AnyError > {
2023-03-11 16:43:45 +00:00
let init_config = json! ( { " debug " : debug } ) ;
2023-01-27 15:43:16 +00:00
let init_src = format! ( " globalThis.serverInit( {init_config} ); " ) ;
2020-12-07 10:46:39 +00:00
2024-03-05 01:17:39 +00:00
runtime . execute_script ( located_script_name! ( ) , init_src ) ? ;
2021-07-08 16:56:53 +00:00
Ok ( ( ) )
2020-12-07 10:46:39 +00:00
}
2022-07-11 23:35:18 +00:00
#[ derive(Debug, Deserialize_repr, Serialize_repr) ]
#[ repr(u32) ]
pub enum CompletionTriggerKind {
Invoked = 1 ,
TriggerCharacter = 2 ,
TriggerForIncompleteCompletions = 3 ,
}
impl From < lsp ::CompletionTriggerKind > for CompletionTriggerKind {
fn from ( kind : lsp ::CompletionTriggerKind ) -> Self {
match kind {
lsp ::CompletionTriggerKind ::INVOKED = > Self ::Invoked ,
lsp ::CompletionTriggerKind ::TRIGGER_CHARACTER = > Self ::TriggerCharacter ,
lsp ::CompletionTriggerKind ::TRIGGER_FOR_INCOMPLETE_COMPLETIONS = > {
Self ::TriggerForIncompleteCompletions
}
_ = > Self ::Invoked ,
}
}
}
2023-10-17 01:51:42 +00:00
pub type QuotePreference = config ::QuoteStyle ;
2023-09-18 19:48:32 +00:00
2023-09-26 02:54:07 +00:00
pub type ImportModuleSpecifierPreference = config ::ImportModuleSpecifier ;
2020-12-08 10:36:13 +00:00
#[ derive(Debug, Serialize) ]
#[ serde(rename_all = " kebab-case " ) ]
#[ allow(dead_code) ]
pub enum ImportModuleSpecifierEnding {
Auto ,
Minimal ,
Index ,
Js ,
}
2022-07-11 23:35:18 +00:00
#[ derive(Debug, Serialize) ]
#[ serde(rename_all = " kebab-case " ) ]
#[ allow(dead_code) ]
pub enum IncludeInlayParameterNameHints {
None ,
Literals ,
All ,
}
2022-10-16 02:39:43 +00:00
impl From < & config ::InlayHintsParamNamesEnabled >
for IncludeInlayParameterNameHints
{
fn from ( setting : & config ::InlayHintsParamNamesEnabled ) -> Self {
match setting {
config ::InlayHintsParamNamesEnabled ::All = > Self ::All ,
config ::InlayHintsParamNamesEnabled ::Literals = > Self ::Literals ,
config ::InlayHintsParamNamesEnabled ::None = > Self ::None ,
}
}
}
2020-12-08 10:36:13 +00:00
#[ derive(Debug, Serialize) ]
#[ serde(rename_all = " kebab-case " ) ]
#[ allow(dead_code) ]
pub enum IncludePackageJsonAutoImports {
Auto ,
On ,
Off ,
}
2023-09-26 02:54:07 +00:00
pub type JsxAttributeCompletionStyle = config ::JsxAttributeCompletionStyle ;
2022-07-11 23:35:18 +00:00
2021-03-15 22:01:41 +00:00
#[ derive(Debug, Default, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct GetCompletionsAtPositionOptions {
#[ serde(flatten) ]
pub user_preferences : UserPreferences ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub trigger_character : Option < String > ,
2022-07-11 23:35:18 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub trigger_kind : Option < CompletionTriggerKind > ,
2021-03-15 22:01:41 +00:00
}
2020-12-08 10:36:13 +00:00
#[ derive(Debug, Default, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct UserPreferences {
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub disable_suggestions : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub quote_preference : Option < QuotePreference > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub include_completions_for_module_exports : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2021-09-16 02:07:52 +00:00
pub include_completions_for_import_statements : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub include_completions_with_snippet_text : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-12-08 10:36:13 +00:00
pub include_automatic_optional_chain_completions : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub include_completions_with_insert_text : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2022-07-11 23:35:18 +00:00
pub include_completions_with_class_member_snippets : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub include_completions_with_object_literal_method_snippets : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub use_label_details_in_completion_entries : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2021-09-16 02:07:52 +00:00
pub allow_incomplete_completions : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-12-08 10:36:13 +00:00
pub import_module_specifier_preference :
Option < ImportModuleSpecifierPreference > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub import_module_specifier_ending : Option < ImportModuleSpecifierEnding > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub allow_text_changes_in_new_files : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub provide_prefix_and_suffix_text_for_rename : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub include_package_json_auto_imports : Option < IncludePackageJsonAutoImports > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub provide_refactor_not_applicable_reason : Option < bool > ,
2022-07-11 23:35:18 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub jsx_attribute_completion_style : Option < JsxAttributeCompletionStyle > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub include_inlay_parameter_name_hints :
Option < IncludeInlayParameterNameHints > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub include_inlay_parameter_name_hints_when_argument_matches_name :
Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub include_inlay_function_parameter_type_hints : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub include_inlay_variable_type_hints : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2022-10-16 02:39:43 +00:00
pub include_inlay_variable_type_hints_when_type_matches_name : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2022-07-11 23:35:18 +00:00
pub include_inlay_property_declaration_type_hints : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub include_inlay_function_like_return_type_hints : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub include_inlay_enum_member_value_hints : Option < bool > ,
2022-10-14 12:04:38 +00:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub allow_rename_of_import_path : Option < bool > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub auto_import_file_exclude_patterns : Option < Vec < String > > ,
2020-12-08 10:36:13 +00:00
}
2023-09-21 05:46:39 +00:00
impl UserPreferences {
2023-09-26 02:54:07 +00:00
pub fn from_config_for_specifier (
config : & config ::Config ,
2023-09-21 05:46:39 +00:00
specifier : & ModuleSpecifier ,
) -> Self {
2024-03-26 15:52:20 +00:00
let fmt_options = config . tree . fmt_options_for_specifier ( specifier ) ;
let fmt_config = & fmt_options . options ;
2023-09-26 02:54:07 +00:00
let base_preferences = Self {
allow_incomplete_completions : Some ( true ) ,
allow_text_changes_in_new_files : Some ( specifier . scheme ( ) = = " file " ) ,
// TODO(nayeemrmn): Investigate why we use `Index` here.
import_module_specifier_ending : Some ( ImportModuleSpecifierEnding ::Index ) ,
include_completions_with_snippet_text : Some (
config . client_capabilities . snippet_support ,
) ,
provide_refactor_not_applicable_reason : Some ( true ) ,
quote_preference : Some ( fmt_config . into ( ) ) ,
use_label_details_in_completion_entries : Some ( true ) ,
2022-10-16 02:39:43 +00:00
.. Default ::default ( )
2023-09-26 02:54:07 +00:00
} ;
2023-10-24 20:27:27 +00:00
let Some ( language_settings ) =
config . language_settings_for_specifier ( specifier )
2023-09-26 02:54:07 +00:00
else {
return base_preferences ;
} ;
Self {
auto_import_file_exclude_patterns : Some (
language_settings
. preferences
. auto_import_file_exclude_patterns
. clone ( ) ,
) ,
include_automatic_optional_chain_completions : Some (
language_settings . suggest . enabled
& & language_settings
. suggest
. include_automatic_optional_chain_completions ,
) ,
include_completions_for_import_statements : Some (
language_settings . suggest . enabled
& & language_settings
. suggest
. include_completions_for_import_statements ,
) ,
include_completions_for_module_exports : Some (
language_settings . suggest . enabled
& & language_settings . suggest . auto_imports ,
) ,
include_completions_with_class_member_snippets : Some (
language_settings . suggest . enabled
& & language_settings . suggest . class_member_snippets . enabled
& & config . client_capabilities . snippet_support ,
) ,
include_completions_with_insert_text : Some (
language_settings . suggest . enabled ,
) ,
include_completions_with_object_literal_method_snippets : Some (
language_settings . suggest . enabled
& & language_settings
. suggest
. object_literal_method_snippets
. enabled
& & config . client_capabilities . snippet_support ,
) ,
import_module_specifier_preference : Some (
language_settings . preferences . import_module_specifier ,
) ,
include_inlay_parameter_name_hints : Some (
( & language_settings . inlay_hints . parameter_names . enabled ) . into ( ) ,
) ,
include_inlay_parameter_name_hints_when_argument_matches_name : Some (
! language_settings
. inlay_hints
. parameter_names
. suppress_when_argument_matches_name ,
) ,
include_inlay_function_parameter_type_hints : Some (
language_settings . inlay_hints . parameter_types . enabled ,
) ,
include_inlay_variable_type_hints : Some (
language_settings . inlay_hints . variable_types . enabled ,
) ,
include_inlay_variable_type_hints_when_type_matches_name : Some (
! language_settings
. inlay_hints
. variable_types
. suppress_when_type_matches_name ,
) ,
include_inlay_property_declaration_type_hints : Some (
language_settings
. inlay_hints
. property_declaration_types
. enabled ,
) ,
include_inlay_function_like_return_type_hints : Some (
language_settings
. inlay_hints
. function_like_return_types
. enabled ,
) ,
include_inlay_enum_member_value_hints : Some (
language_settings . inlay_hints . enum_member_values . enabled ,
) ,
jsx_attribute_completion_style : Some (
language_settings . preferences . jsx_attribute_completion_style ,
) ,
provide_prefix_and_suffix_text_for_rename : Some (
language_settings . preferences . use_aliases_for_renames ,
) ,
2023-10-17 01:51:42 +00:00
// Only use workspace settings for quote style if there's no `deno.json`.
2024-04-02 22:02:50 +00:00
quote_preference : if config
. tree
. config_file_for_specifier ( specifier )
. is_some ( )
2024-03-26 15:52:20 +00:00
{
2023-10-17 01:51:42 +00:00
base_preferences . quote_preference
} else {
Some ( language_settings . preferences . quote_style )
} ,
2023-09-26 02:54:07 +00:00
.. base_preferences
2022-10-16 02:39:43 +00:00
}
}
}
2021-02-16 02:34:09 +00:00
#[ derive(Debug, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct SignatureHelpItemsOptions {
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub trigger_reason : Option < SignatureHelpTriggerReason > ,
}
#[ derive(Debug, Serialize) ]
pub enum SignatureHelpTriggerKind {
#[ serde(rename = " characterTyped " ) ]
CharacterTyped ,
#[ serde(rename = " invoked " ) ]
Invoked ,
#[ serde(rename = " retrigger " ) ]
Retrigger ,
2021-11-25 01:10:12 +00:00
#[ serde(rename = " unknown " ) ]
Unknown ,
2021-02-16 02:34:09 +00:00
}
impl From < lsp ::SignatureHelpTriggerKind > for SignatureHelpTriggerKind {
fn from ( kind : lsp ::SignatureHelpTriggerKind ) -> Self {
match kind {
2021-11-25 01:10:12 +00:00
lsp ::SignatureHelpTriggerKind ::INVOKED = > Self ::Invoked ,
lsp ::SignatureHelpTriggerKind ::TRIGGER_CHARACTER = > Self ::CharacterTyped ,
lsp ::SignatureHelpTriggerKind ::CONTENT_CHANGE = > Self ::Retrigger ,
_ = > Self ::Unknown ,
2021-02-16 02:34:09 +00:00
}
}
}
#[ derive(Debug, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct SignatureHelpTriggerReason {
pub kind : SignatureHelpTriggerKind ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub trigger_character : Option < String > ,
}
2021-03-15 22:01:41 +00:00
#[ derive(Debug, Serialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct GetCompletionDetailsArgs {
pub specifier : ModuleSpecifier ,
pub position : u32 ,
pub name : String ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2023-08-17 14:46:11 +00:00
pub format_code_settings : Option < FormatCodeSettings > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2021-03-15 22:01:41 +00:00
pub source : Option < String > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2022-07-11 23:35:18 +00:00
pub preferences : Option < UserPreferences > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2021-03-15 22:01:41 +00:00
pub data : Option < Value > ,
}
2022-07-11 23:35:18 +00:00
impl From < & CompletionItemData > for GetCompletionDetailsArgs {
fn from ( item_data : & CompletionItemData ) -> Self {
2021-03-15 22:01:41 +00:00
Self {
2022-07-11 23:35:18 +00:00
specifier : item_data . specifier . clone ( ) ,
2021-03-15 22:01:41 +00:00
position : item_data . position ,
2022-07-11 23:35:18 +00:00
name : item_data . name . clone ( ) ,
source : item_data . source . clone ( ) ,
preferences : None ,
2023-08-17 14:46:11 +00:00
format_code_settings : None ,
2022-07-11 23:35:18 +00:00
data : item_data . data . clone ( ) ,
2021-03-15 22:01:41 +00:00
}
}
}
2023-05-12 23:07:40 +00:00
#[ derive(Debug) ]
pub struct GetNavigateToItemsArgs {
pub search : String ,
pub max_result_count : Option < u32 > ,
pub file : Option < String > ,
}
2024-04-23 15:50:30 +00:00
#[ derive(Serialize, Clone, Copy) ]
pub struct TscTextRange {
pos : u32 ,
end : u32 ,
2020-12-07 10:46:39 +00:00
}
2024-04-23 15:50:30 +00:00
impl From < Range < u32 > > for TscTextRange {
fn from ( range : Range < u32 > ) -> Self {
Self {
pos : range . start ,
end : range . end ,
}
}
}
#[ derive(Serialize, Clone) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CombinedCodeFixScope {
r#type : & 'static str ,
file_name : String ,
}
#[ derive(Serialize, Clone, Copy) ]
pub struct JsNull ;
#[ derive(Serialize) ]
pub enum TscRequest {
GetDiagnostics ( ( Vec < String > , usize ) ) ,
GetAssets ,
CleanupSemanticCache ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6230
FindReferences ( ( String , u32 ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6235
GetNavigationTree ( ( String , ) ) ,
GetSupportedCodeFixes ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6214
GetQuickInfoAtPosition ( ( String , u32 ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6257
GetCodeFixesAtPosition (
Box < (
String ,
u32 ,
u32 ,
Vec < String > ,
FormatCodeSettings ,
UserPreferences ,
) > ,
) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6274
GetApplicableRefactors (
Box < (
String ,
TscTextRange ,
UserPreferences ,
Option < & 'static str > ,
String ,
) > ,
) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6258
GetCombinedCodeFix (
Box < (
CombinedCodeFixScope ,
String ,
FormatCodeSettings ,
UserPreferences ,
) > ,
) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6275
GetEditsForRefactor (
Box < (
String ,
FormatCodeSettings ,
TscTextRange ,
String ,
String ,
Option < UserPreferences > ,
) > ,
) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6281
GetEditsForFileRename (
Box < ( String , String , FormatCodeSettings , UserPreferences ) > ,
) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6231
GetDocumentHighlights ( Box < ( String , u32 , Vec < String > ) > ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6226
GetDefinitionAndBoundSpan ( ( String , u32 ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6227
GetTypeDefinitionAtPosition ( ( String , u32 ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6193
GetCompletionsAtPosition (
Box < (
String ,
u32 ,
GetCompletionsAtPositionOptions ,
FormatCodeSettings ,
) > ,
) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6205
#[ allow(clippy::type_complexity) ]
GetCompletionEntryDetails (
Box < (
String ,
u32 ,
String ,
FormatCodeSettings ,
Option < String > ,
Option < UserPreferences > ,
Option < Value > ,
) > ,
) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6228
GetImplementationAtPosition ( ( String , u32 ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6240
GetOutliningSpans ( ( String , ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6237
ProvideCallHierarchyIncomingCalls ( ( String , u32 ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6238
ProvideCallHierarchyOutgoingCalls ( ( String , u32 ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6236
PrepareCallHierarchy ( ( String , u32 ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6221
FindRenameLocations ( ( String , u32 , bool , bool , bool ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6224
GetSmartSelectionRange ( ( String , u32 ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6183
GetEncodedSemanticClassifications ( ( String , TextSpan , & 'static str ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6217
GetSignatureHelpItems ( ( String , u32 , SignatureHelpItemsOptions ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6233
GetNavigateToItems ( ( String , Option < u32 > , Option < String > ) ) ,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6239
ProvideInlayHints ( ( String , TextSpan , UserPreferences ) ) ,
}
impl TscRequest {
/// Converts the request into a tuple containing the method name and the
/// arguments (in the form of a V8 value) to be passed to the server request
/// function
fn to_server_request < ' s > (
& self ,
scope : & mut v8 ::HandleScope < ' s > ,
) -> Result < ( & 'static str , Option < v8 ::Local < ' s , v8 ::Value > > ) , AnyError > {
let args = match self {
TscRequest ::GetDiagnostics ( args ) = > {
( " $getDiagnostics " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::FindReferences ( args ) = > {
( " findReferences " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::GetNavigationTree ( args ) = > {
( " getNavigationTree " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::GetSupportedCodeFixes = > ( " $getSupportedCodeFixes " , None ) ,
TscRequest ::GetQuickInfoAtPosition ( args ) = > (
" getQuickInfoAtPosition " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::GetCodeFixesAtPosition ( args ) = > (
" getCodeFixesAtPosition " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::GetApplicableRefactors ( args ) = > (
" getApplicableRefactors " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::GetCombinedCodeFix ( args ) = > {
( " getCombinedCodeFix " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::GetEditsForRefactor ( args ) = > {
( " getEditsForRefactor " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::GetEditsForFileRename ( args ) = > {
( " getEditsForFileRename " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::GetDocumentHighlights ( args ) = > {
( " getDocumentHighlights " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::GetDefinitionAndBoundSpan ( args ) = > (
" getDefinitionAndBoundSpan " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::GetTypeDefinitionAtPosition ( args ) = > (
" getTypeDefinitionAtPosition " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::GetCompletionsAtPosition ( args ) = > (
" getCompletionsAtPosition " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::GetCompletionEntryDetails ( args ) = > (
" getCompletionEntryDetails " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::GetImplementationAtPosition ( args ) = > (
" getImplementationAtPosition " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::GetOutliningSpans ( args ) = > {
( " getOutliningSpans " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::ProvideCallHierarchyIncomingCalls ( args ) = > (
" provideCallHierarchyIncomingCalls " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::ProvideCallHierarchyOutgoingCalls ( args ) = > (
" provideCallHierarchyOutgoingCalls " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::PrepareCallHierarchy ( args ) = > {
( " prepareCallHierarchy " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::FindRenameLocations ( args ) = > {
( " findRenameLocations " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::GetSmartSelectionRange ( args ) = > (
" getSmartSelectionRange " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::GetEncodedSemanticClassifications ( args ) = > (
" getEncodedSemanticClassifications " ,
Some ( serde_v8 ::to_v8 ( scope , args ) ? ) ,
) ,
TscRequest ::GetSignatureHelpItems ( args ) = > {
( " getSignatureHelpItems " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::GetNavigateToItems ( args ) = > {
( " getNavigateToItems " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::ProvideInlayHints ( args ) = > {
( " provideInlayHints " , Some ( serde_v8 ::to_v8 ( scope , args ) ? ) )
}
TscRequest ::CleanupSemanticCache = > ( " cleanupSemanticCache " , None ) ,
TscRequest ::GetAssets = > ( " $getAssets " , None ) ,
} ;
Ok ( args )
}
fn method ( & self ) -> & 'static str {
match self {
TscRequest ::GetDiagnostics ( _ ) = > " $getDiagnostics " ,
TscRequest ::CleanupSemanticCache = > " cleanupSemanticCache " ,
TscRequest ::FindReferences ( _ ) = > " findReferences " ,
TscRequest ::GetNavigationTree ( _ ) = > " getNavigationTree " ,
TscRequest ::GetSupportedCodeFixes = > " $getSupportedCodeFixes " ,
TscRequest ::GetQuickInfoAtPosition ( _ ) = > " getQuickInfoAtPosition " ,
TscRequest ::GetCodeFixesAtPosition ( _ ) = > " getCodeFixesAtPosition " ,
TscRequest ::GetApplicableRefactors ( _ ) = > " getApplicableRefactors " ,
TscRequest ::GetCombinedCodeFix ( _ ) = > " getCombinedCodeFix " ,
TscRequest ::GetEditsForRefactor ( _ ) = > " getEditsForRefactor " ,
TscRequest ::GetEditsForFileRename ( _ ) = > " getEditsForFileRename " ,
TscRequest ::GetDocumentHighlights ( _ ) = > " getDocumentHighlights " ,
TscRequest ::GetDefinitionAndBoundSpan ( _ ) = > " getDefinitionAndBoundSpan " ,
TscRequest ::GetTypeDefinitionAtPosition ( _ ) = > {
" getTypeDefinitionAtPosition "
}
TscRequest ::GetCompletionsAtPosition ( _ ) = > " getCompletionsAtPosition " ,
TscRequest ::GetCompletionEntryDetails ( _ ) = > " getCompletionEntryDetails " ,
TscRequest ::GetImplementationAtPosition ( _ ) = > {
" getImplementationAtPosition "
}
TscRequest ::GetOutliningSpans ( _ ) = > " getOutliningSpans " ,
TscRequest ::ProvideCallHierarchyIncomingCalls ( _ ) = > {
" provideCallHierarchyIncomingCalls "
}
TscRequest ::ProvideCallHierarchyOutgoingCalls ( _ ) = > {
" provideCallHierarchyOutgoingCalls "
}
TscRequest ::PrepareCallHierarchy ( _ ) = > " prepareCallHierarchy " ,
TscRequest ::FindRenameLocations ( _ ) = > " findRenameLocations " ,
TscRequest ::GetSmartSelectionRange ( _ ) = > " getSmartSelectionRange " ,
TscRequest ::GetEncodedSemanticClassifications ( _ ) = > {
" getEncodedSemanticClassifications "
}
TscRequest ::GetSignatureHelpItems ( _ ) = > " getSignatureHelpItems " ,
TscRequest ::GetNavigateToItems ( _ ) = > " getNavigateToItems " ,
TscRequest ::ProvideInlayHints ( _ ) = > " provideInlayHints " ,
TscRequest ::GetAssets = > " $getAssets " ,
}
2023-12-03 22:07:40 +00:00
}
2020-12-07 10:46:39 +00:00
}
#[ cfg(test) ]
mod tests {
use super ::* ;
2023-08-02 00:49:09 +00:00
use crate ::cache ::GlobalHttpCache ;
2022-11-28 22:28:54 +00:00
use crate ::cache ::HttpCache ;
2023-08-08 14:23:02 +00:00
use crate ::cache ::RealDenoCacheEnv ;
2021-03-10 10:39:16 +00:00
use crate ::http_util ::HeadersMap ;
2023-07-08 20:06:45 +00:00
use crate ::lsp ::cache ::CacheMetadata ;
2024-04-26 20:39:33 +00:00
use crate ::lsp ::config ::Config ;
2022-10-28 18:48:14 +00:00
use crate ::lsp ::config ::WorkspaceSettings ;
2021-10-28 23:56:01 +00:00
use crate ::lsp ::documents ::Documents ;
2021-06-02 10:29:58 +00:00
use crate ::lsp ::documents ::LanguageId ;
2024-04-26 20:39:33 +00:00
use crate ::lsp ::resolver ::LspResolver ;
2021-03-15 22:01:41 +00:00
use crate ::lsp ::text ::LineIndex ;
2023-01-14 14:36:19 +00:00
use pretty_assertions ::assert_eq ;
2021-03-10 10:39:16 +00:00
use std ::path ::Path ;
2022-04-01 15:15:37 +00:00
use test_util ::TempDir ;
2021-03-10 10:39:16 +00:00
2024-04-02 22:02:50 +00:00
async fn mock_state_snapshot (
2021-06-02 10:29:58 +00:00
fixtures : & [ ( & str , & str , i32 , LanguageId ) ] ,
2021-03-10 10:39:16 +00:00
location : & Path ,
2024-04-02 22:02:50 +00:00
ts_config : Value ,
2021-03-10 10:39:16 +00:00
) -> StateSnapshot {
2023-08-08 14:23:02 +00:00
let cache = Arc ::new ( GlobalHttpCache ::new (
location . to_path_buf ( ) ,
RealDenoCacheEnv ,
) ) ;
2023-07-08 20:06:45 +00:00
let mut documents = Documents ::new ( cache . clone ( ) ) ;
2021-06-02 10:29:58 +00:00
for ( specifier , source , version , language_id ) in fixtures {
2021-02-17 18:47:18 +00:00
let specifier =
resolve_url ( specifier ) . expect ( " failed to create specifier " ) ;
2021-09-07 14:39:32 +00:00
documents . open (
specifier . clone ( ) ,
* version ,
2022-12-20 20:19:35 +00:00
* language_id ,
2022-05-20 20:40:55 +00:00
( * source ) . into ( ) ,
2021-09-07 14:39:32 +00:00
) ;
2020-12-07 10:46:39 +00:00
}
2024-04-26 20:39:33 +00:00
let mut config = Config ::default ( ) ;
2024-04-02 22:02:50 +00:00
config
. tree
. inject_config_file (
deno_config ::ConfigFile ::new (
& json! ( {
" compilerOptions " : ts_config ,
} )
. to_string ( ) ,
resolve_url ( " file:///deno.json " ) . unwrap ( ) ,
& deno_config ::ParseOptions ::default ( ) ,
)
. unwrap ( ) ,
)
. await ;
2024-04-26 20:39:33 +00:00
let resolver = LspResolver ::default ( )
2024-05-01 05:54:25 +00:00
. with_new_config ( & config , None , None )
2024-04-26 20:39:33 +00:00
. await ;
2020-12-21 13:44:26 +00:00
StateSnapshot {
2024-04-15 00:07:04 +00:00
project_version : 0 ,
2021-01-25 23:47:12 +00:00
documents ,
2023-07-08 20:06:45 +00:00
assets : Default ::default ( ) ,
cache_metadata : CacheMetadata ::new ( cache ) ,
2024-04-27 20:35:41 +00:00
config : Arc ::new ( config ) ,
2024-04-26 20:39:33 +00:00
resolver ,
2020-12-07 10:46:39 +00:00
}
}
2023-10-02 06:32:05 +00:00
async fn setup (
2022-04-01 15:15:37 +00:00
temp_dir : & TempDir ,
2020-12-07 10:46:39 +00:00
config : Value ,
2021-06-02 10:29:58 +00:00
sources : & [ ( & str , & str , i32 , LanguageId ) ] ,
2023-10-02 06:32:05 +00:00
) -> ( TsServer , Arc < StateSnapshot > , Arc < GlobalHttpCache > ) {
2023-06-10 15:09:45 +00:00
let location = temp_dir . path ( ) . join ( " deps " ) . to_path_buf ( ) ;
2023-08-08 14:23:02 +00:00
let cache =
Arc ::new ( GlobalHttpCache ::new ( location . clone ( ) , RealDenoCacheEnv ) ) ;
2024-04-02 22:02:50 +00:00
let snapshot =
Arc ::new ( mock_state_snapshot ( sources , & location , config ) . await ) ;
2023-10-02 06:32:05 +00:00
let performance = Arc ::new ( Performance ::default ( ) ) ;
2024-04-02 22:02:50 +00:00
let ts_server = TsServer ::new ( performance , cache . clone ( ) ) ;
2024-04-11 23:17:10 +00:00
ts_server . start ( None ) . unwrap ( ) ;
2023-10-02 06:32:05 +00:00
( ts_server , snapshot , cache )
2020-12-07 10:46:39 +00:00
}
2024-04-11 20:55:27 +00:00
fn setup_op_state ( state_snapshot : Arc < StateSnapshot > ) -> OpState {
let state =
State ::new ( state_snapshot , Default ::default ( ) , Default ::default ( ) ) ;
let mut op_state = OpState ::new ( None ) ;
op_state . put ( state ) ;
op_state
}
2020-12-07 10:46:39 +00:00
#[ test ]
fn test_replace_links ( ) {
let actual = replace_links ( r "test {@link http://deno.land/x/mod.ts} test" ) ;
assert_eq! (
actual ,
r "test [http://deno.land/x/mod.ts](http://deno.land/x/mod.ts) test"
) ;
let actual =
replace_links ( r "test {@link http://deno.land/x/mod.ts a link} test" ) ;
assert_eq! ( actual , r "test [a link](http://deno.land/x/mod.ts) test" ) ;
let actual =
replace_links ( r "test {@linkcode http://deno.land/x/mod.ts a link} test" ) ;
assert_eq! ( actual , r "test [`a link`](http://deno.land/x/mod.ts) test" ) ;
}
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_get_diagnostics ( ) {
2022-04-01 15:15:37 +00:00
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , _ ) = setup (
2022-04-01 15:15:37 +00:00
& temp_dir ,
2020-12-07 10:46:39 +00:00
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" noEmit " : true ,
2024-03-26 15:52:20 +00:00
" lib " : [ ] ,
2020-12-07 10:46:39 +00:00
} ) ,
2021-06-02 10:29:58 +00:00
& [ (
" file:///a.ts " ,
r # "console.log("hello deno");"# ,
1 ,
LanguageId ::TypeScript ,
) ] ,
2023-10-02 06:32:05 +00:00
)
. await ;
2021-02-17 18:47:18 +00:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2023-10-02 06:32:05 +00:00
let diagnostics = ts_server
. get_diagnostics ( snapshot , vec! [ specifier ] , Default ::default ( ) )
. await
. unwrap ( ) ;
2020-12-07 10:46:39 +00:00
assert_eq! (
2023-10-02 06:32:05 +00:00
json! ( diagnostics ) ,
2021-01-22 10:03:16 +00:00
json! ( {
" file:///a.ts " : [
{
" start " : {
" line " : 0 ,
" character " : 0 ,
} ,
" end " : {
" line " : 0 ,
" character " : 7
} ,
" fileName " : " file:///a.ts " ,
2021-05-27 23:33:11 +00:00
" messageText " : " Cannot find name 'console'. Do you need to change your target library? Try changing the \' lib \' compiler option to include 'dom'. " ,
2021-01-22 10:03:16 +00:00
" sourceLine " : " console.log( \" hello deno \" ); " ,
" category " : 1 ,
" code " : 2584
}
]
} )
2020-12-07 10:46:39 +00:00
) ;
}
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_get_diagnostics_lib ( ) {
2022-04-01 15:15:37 +00:00
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , _ ) = setup (
2022-04-01 15:15:37 +00:00
& temp_dir ,
2021-02-12 11:49:42 +00:00
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" jsx " : " react " ,
" lib " : [ " esnext " , " dom " , " deno.ns " ] ,
" noEmit " : true ,
} ) ,
2021-06-02 10:29:58 +00:00
& [ (
" file:///a.ts " ,
r # "console.log(document.location);"# ,
1 ,
LanguageId ::TypeScript ,
) ] ,
2023-10-02 06:32:05 +00:00
)
. await ;
2021-02-17 18:47:18 +00:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2023-10-02 06:32:05 +00:00
let diagnostics = ts_server
. get_diagnostics ( snapshot , vec! [ specifier ] , Default ::default ( ) )
. await
. unwrap ( ) ;
assert_eq! ( json! ( diagnostics ) , json! ( { " file:///a.ts " : [ ] } ) ) ;
2021-02-12 11:49:42 +00:00
}
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_module_resolution ( ) {
2022-04-01 15:15:37 +00:00
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , _ ) = setup (
2022-04-01 15:15:37 +00:00
& temp_dir ,
2020-12-07 10:46:39 +00:00
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
2021-03-10 10:39:16 +00:00
& [ (
2020-12-07 10:46:39 +00:00
" file:///a.ts " ,
r #"
import { B } from " https://deno.land/x/b/mod.ts " ;
const b = new B ( ) ;
console . log ( b ) ;
" #,
1 ,
2021-06-02 10:29:58 +00:00
LanguageId ::TypeScript ,
2020-12-07 10:46:39 +00:00
) ] ,
2023-10-02 06:32:05 +00:00
)
. await ;
2021-02-17 18:47:18 +00:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2023-10-02 06:32:05 +00:00
let diagnostics = ts_server
. get_diagnostics ( snapshot , vec! [ specifier ] , Default ::default ( ) )
. await
. unwrap ( ) ;
assert_eq! ( json! ( diagnostics ) , json! ( { " file:///a.ts " : [ ] } ) ) ;
2020-12-07 10:46:39 +00:00
}
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_bad_module_specifiers ( ) {
2022-04-01 15:15:37 +00:00
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , _ ) = setup (
2022-04-01 15:15:37 +00:00
& temp_dir ,
2020-12-07 10:46:39 +00:00
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
2021-03-10 10:39:16 +00:00
& [ (
2020-12-07 10:46:39 +00:00
" file:///a.ts " ,
r #"
import { A } from " . " ;
" #,
1 ,
2021-06-02 10:29:58 +00:00
LanguageId ::TypeScript ,
2020-12-07 10:46:39 +00:00
) ] ,
2023-10-02 06:32:05 +00:00
)
. await ;
2021-02-17 18:47:18 +00:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2023-10-02 06:32:05 +00:00
let diagnostics = ts_server
. get_diagnostics ( snapshot , vec! [ specifier ] , Default ::default ( ) )
. await
. unwrap ( ) ;
2020-12-30 01:46:58 +00:00
assert_eq! (
2023-10-02 06:32:05 +00:00
json! ( diagnostics ) ,
2021-01-22 10:03:16 +00:00
json! ( {
" file:///a.ts " : [ {
" start " : {
" line " : 1 ,
" character " : 8
} ,
" end " : {
" line " : 1 ,
" character " : 30
} ,
" fileName " : " file:///a.ts " ,
" messageText " : " \' A \' is declared but its value is never read. " ,
" sourceLine " : " import { A } from \" . \" ; " ,
" category " : 2 ,
" code " : 6133 ,
} ]
} )
2020-12-30 01:46:58 +00:00
) ;
2020-12-07 10:46:39 +00:00
}
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_remote_modules ( ) {
2022-04-01 15:15:37 +00:00
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , _ ) = setup (
2022-04-01 15:15:37 +00:00
& temp_dir ,
2020-12-07 10:46:39 +00:00
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
2021-03-10 10:39:16 +00:00
& [ (
2020-12-07 10:46:39 +00:00
" file:///a.ts " ,
r #"
import { B } from " https://deno.land/x/b/mod.ts " ;
const b = new B ( ) ;
console . log ( b ) ;
" #,
1 ,
2021-06-02 10:29:58 +00:00
LanguageId ::TypeScript ,
2020-12-07 10:46:39 +00:00
) ] ,
2023-10-02 06:32:05 +00:00
)
. await ;
2021-02-17 18:47:18 +00:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2023-10-02 06:32:05 +00:00
let diagnostics = ts_server
. get_diagnostics ( snapshot , vec! [ specifier ] , Default ::default ( ) )
. await
. unwrap ( ) ;
assert_eq! ( json! ( diagnostics ) , json! ( { " file:///a.ts " : [ ] } ) ) ;
2020-12-07 10:46:39 +00:00
}
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_partial_modules ( ) {
2022-04-01 15:15:37 +00:00
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , _ ) = setup (
2022-04-01 15:15:37 +00:00
& temp_dir ,
2020-12-07 10:46:39 +00:00
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
2021-03-10 10:39:16 +00:00
& [ (
2020-12-07 10:46:39 +00:00
" file:///a.ts " ,
r #"
import {
Application ,
Context ,
Router ,
Status ,
} from " https://deno.land/x/oak@v6.3.2/mod.ts " ;
2021-01-29 19:34:33 +00:00
2020-12-07 10:46:39 +00:00
import * as test from
" #,
1 ,
2021-06-02 10:29:58 +00:00
LanguageId ::TypeScript ,
2020-12-07 10:46:39 +00:00
) ] ,
2023-10-02 06:32:05 +00:00
)
. await ;
2021-02-17 18:47:18 +00:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2023-10-02 06:32:05 +00:00
let diagnostics = ts_server
. get_diagnostics ( snapshot , vec! [ specifier ] , Default ::default ( ) )
. await
. unwrap ( ) ;
2020-12-15 19:34:39 +00:00
assert_eq! (
2023-10-02 06:32:05 +00:00
json! ( diagnostics ) ,
2021-01-22 10:03:16 +00:00
json! ( {
" file:///a.ts " : [ {
" start " : {
" line " : 1 ,
" character " : 8
} ,
" end " : {
" line " : 6 ,
" character " : 55 ,
} ,
" fileName " : " file:///a.ts " ,
" messageText " : " All imports in import declaration are unused. " ,
" sourceLine " : " import { " ,
" category " : 2 ,
" code " : 6192 ,
} , {
" start " : {
" line " : 8 ,
" character " : 29
} ,
" end " : {
" line " : 8 ,
" character " : 29
} ,
" fileName " : " file:///a.ts " ,
" messageText " : " Expression expected. " ,
" sourceLine " : " import * as test from " ,
" category " : 1 ,
" code " : 1109
} ]
} )
2020-12-15 19:34:39 +00:00
) ;
}
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_no_debug_failure ( ) {
2022-04-01 15:15:37 +00:00
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , _ ) = setup (
2022-04-01 15:15:37 +00:00
& temp_dir ,
2020-12-30 01:46:58 +00:00
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
2021-06-02 10:29:58 +00:00
& [ (
" file:///a.ts " ,
r # "const url = new URL("b.js", import."# ,
1 ,
LanguageId ::TypeScript ,
) ] ,
2023-10-02 06:32:05 +00:00
)
. await ;
2021-02-17 18:47:18 +00:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2023-10-02 06:32:05 +00:00
let diagnostics = ts_server
. get_diagnostics ( snapshot , vec! [ specifier ] , Default ::default ( ) )
. await
. unwrap ( ) ;
2021-08-27 00:12:59 +00:00
assert_eq! (
2023-10-02 06:32:05 +00:00
json! ( diagnostics ) ,
2021-08-27 00:12:59 +00:00
json! ( {
" file:///a.ts " : [
{
" start " : {
" line " : 0 ,
" character " : 35 ,
} ,
" end " : {
" line " : 0 ,
" character " : 35
} ,
" fileName " : " file:///a.ts " ,
" messageText " : " Identifier expected. " ,
" sourceLine " : " const url = new URL( \" b.js \" , import. " ,
" category " : 1 ,
" code " : 1003 ,
}
]
} )
) ;
2020-12-30 01:46:58 +00:00
}
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_request_assets ( ) {
2022-04-01 15:15:37 +00:00
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , _ ) = setup ( & temp_dir , json! ( { } ) , & [ ] ) . await ;
let assets = get_isolate_assets ( & ts_server , snapshot ) . await ;
2023-01-14 14:36:19 +00:00
let mut asset_names = assets
. iter ( )
. map ( | a | {
2023-10-02 06:32:05 +00:00
a . specifier ( )
. to_string ( )
2023-01-14 14:36:19 +00:00
. replace ( " asset:///lib. " , " " )
. replace ( " .d.ts " , " " )
} )
. collect ::< Vec < _ > > ( ) ;
let mut expected_asset_names : Vec < String > = serde_json ::from_str (
include_str! ( concat! ( env! ( " OUT_DIR " ) , " /lib_file_names.json " ) ) ,
)
. unwrap ( ) ;
asset_names . sort ( ) ;
2022-04-25 15:23:24 +00:00
2024-03-26 22:52:57 +00:00
// if this test fails, update build.rs
2023-10-02 06:32:05 +00:00
expected_asset_names . sort ( ) ;
2023-01-14 14:36:19 +00:00
assert_eq! ( asset_names , expected_asset_names ) ;
2022-04-25 15:23:24 +00:00
// get some notification when the size of the assets grows
let mut total_size = 0 ;
for asset in assets {
2023-10-02 06:32:05 +00:00
total_size + = asset . text ( ) . len ( ) ;
2022-04-25 15:23:24 +00:00
}
assert! ( total_size > 0 ) ;
assert! ( total_size < 2_000_000 ) ; // currently as of TS 4.6, it's 0.7MB
2020-12-07 10:46:39 +00:00
}
2021-03-10 10:39:16 +00:00
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_modify_sources ( ) {
2022-04-01 15:15:37 +00:00
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , cache ) = setup (
2022-04-01 15:15:37 +00:00
& temp_dir ,
2021-03-10 10:39:16 +00:00
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
& [ (
" file:///a.ts " ,
r #"
import * as a from " https://deno.land/x/example/a.ts " ;
if ( a . a = = = " b " ) {
console . log ( " fail " ) ;
}
" #,
1 ,
2021-06-02 10:29:58 +00:00
LanguageId ::TypeScript ,
2021-03-10 10:39:16 +00:00
) ] ,
2023-10-02 06:32:05 +00:00
)
. await ;
2021-03-10 10:39:16 +00:00
let specifier_dep =
resolve_url ( " https://deno.land/x/example/a.ts " ) . unwrap ( ) ;
cache
. set (
& specifier_dep ,
HeadersMap ::default ( ) ,
b " export const b = \" b \" ; \n " ,
)
. unwrap ( ) ;
let specifier = resolve_url ( " file:///a.ts " ) . unwrap ( ) ;
2023-10-02 06:32:05 +00:00
let diagnostics = ts_server
. get_diagnostics ( snapshot . clone ( ) , vec! [ specifier ] , Default ::default ( ) )
. await
. unwrap ( ) ;
2021-03-10 10:39:16 +00:00
assert_eq! (
2023-10-02 06:32:05 +00:00
json! ( diagnostics ) ,
2021-03-10 10:39:16 +00:00
json! ( {
" file:///a.ts " : [
{
" start " : {
" line " : 2 ,
" character " : 16 ,
} ,
" end " : {
" line " : 2 ,
" character " : 17
} ,
" fileName " : " file:///a.ts " ,
" messageText " : " Property \' a \' does not exist on type \' typeof import( \" https://deno.land/x/example/a \" ) \' . " ,
" sourceLine " : " if (a.a === \" b \" ) { " ,
" code " : 2339 ,
" category " : 1 ,
}
]
} )
) ;
cache
. set (
& specifier_dep ,
HeadersMap ::default ( ) ,
b " export const b = \" b \" ; \n \n export const a = \" b \" ; \n " ,
)
. unwrap ( ) ;
2024-01-11 17:07:44 +00:00
let snapshot = {
Arc ::new ( StateSnapshot {
2024-04-15 00:07:04 +00:00
project_version : snapshot . project_version + 1 ,
2024-01-11 17:07:44 +00:00
.. snapshot . as_ref ( ) . clone ( )
} )
} ;
2024-04-22 15:03:16 +00:00
ts_server . project_changed (
snapshot . clone ( ) ,
[ ( & specifier_dep , ChangeKind ::Opened ) ] ,
false ,
) ;
2021-03-10 10:39:16 +00:00
let specifier = resolve_url ( " file:///a.ts " ) . unwrap ( ) ;
2023-10-02 06:32:05 +00:00
let diagnostics = ts_server
. get_diagnostics ( snapshot . clone ( ) , vec! [ specifier ] , Default ::default ( ) )
. await
. unwrap ( ) ;
2021-03-10 10:39:16 +00:00
assert_eq! (
2023-10-02 06:32:05 +00:00
json! ( diagnostics ) ,
2021-03-10 10:39:16 +00:00
json! ( {
" file:///a.ts " : [ ]
} )
) ;
}
2021-03-15 22:01:41 +00:00
#[ test ]
fn test_completion_entry_filter_text ( ) {
let fixture = CompletionEntry {
kind : ScriptElementKind ::MemberVariableElement ,
name : " ['foo'] " . to_string ( ) ,
insert_text : Some ( " ['foo'] " . to_string ( ) ) ,
.. Default ::default ( )
} ;
let actual = fixture . get_filter_text ( ) ;
assert_eq! ( actual , Some ( " .foo " . to_string ( ) ) ) ;
2021-09-16 02:07:52 +00:00
let fixture = CompletionEntry {
kind : ScriptElementKind ::MemberVariableElement ,
name : " #abc " . to_string ( ) ,
.. Default ::default ( )
} ;
let actual = fixture . get_filter_text ( ) ;
2022-12-29 21:22:47 +00:00
assert_eq! ( actual , None ) ;
2021-09-16 02:07:52 +00:00
let fixture = CompletionEntry {
kind : ScriptElementKind ::MemberVariableElement ,
name : " #abc " . to_string ( ) ,
insert_text : Some ( " this.#abc " . to_string ( ) ) ,
.. Default ::default ( )
} ;
let actual = fixture . get_filter_text ( ) ;
assert_eq! ( actual , Some ( " abc " . to_string ( ) ) ) ;
2021-03-15 22:01:41 +00:00
}
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_completions ( ) {
2021-03-15 22:01:41 +00:00
let fixture = r #"
import { B } from " https://deno.land/x/b/mod.ts " ;
const b = new B ( ) ;
console .
" #;
let line_index = LineIndex ::new ( fixture ) ;
let position = line_index
. offset_tsc ( lsp ::Position {
line : 5 ,
character : 16 ,
} )
. unwrap ( ) ;
2022-04-01 15:15:37 +00:00
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , _ ) = setup (
2022-04-01 15:15:37 +00:00
& temp_dir ,
2021-03-15 22:01:41 +00:00
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
2021-06-02 10:29:58 +00:00
& [ ( " file:///a.ts " , fixture , 1 , LanguageId ::TypeScript ) ] ,
2023-10-02 06:32:05 +00:00
)
. await ;
2021-03-15 22:01:41 +00:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2023-10-02 06:32:05 +00:00
let info = ts_server
. get_completions (
snapshot . clone ( ) ,
2021-03-15 22:01:41 +00:00
specifier . clone ( ) ,
position ,
GetCompletionsAtPositionOptions {
user_preferences : UserPreferences {
include_completions_with_insert_text : Some ( true ) ,
.. Default ::default ( )
} ,
trigger_character : Some ( " . " . to_string ( ) ) ,
2022-07-11 23:35:18 +00:00
trigger_kind : None ,
2021-03-15 22:01:41 +00:00
} ,
2023-08-17 14:46:11 +00:00
Default ::default ( ) ,
2023-10-02 06:32:05 +00:00
)
. await
. unwrap ( ) ;
assert_eq! ( info . entries . len ( ) , 22 ) ;
let details = ts_server
. get_completion_details (
snapshot . clone ( ) ,
GetCompletionDetailsArgs {
specifier ,
position ,
name : " log " . to_string ( ) ,
format_code_settings : None ,
source : None ,
preferences : None ,
data : None ,
} ,
)
. await
. unwrap ( )
. unwrap ( ) ;
2021-03-15 22:01:41 +00:00
assert_eq! (
2023-10-02 06:32:05 +00:00
json! ( details ) ,
2021-03-15 22:01:41 +00:00
json! ( {
" name " : " log " ,
" kindModifiers " : " declare " ,
" kind " : " method " ,
" displayParts " : [
{
" text " : " ( " ,
" kind " : " punctuation "
} ,
{
" text " : " method " ,
" kind " : " text "
} ,
{
" text " : " ) " ,
" kind " : " punctuation "
} ,
{
" text " : " " ,
" kind " : " space "
} ,
{
" text " : " Console " ,
" kind " : " interfaceName "
} ,
{
" text " : " . " ,
" kind " : " punctuation "
} ,
{
" text " : " log " ,
" kind " : " methodName "
} ,
{
" text " : " ( " ,
" kind " : " punctuation "
} ,
{
" text " : " ... " ,
" kind " : " punctuation "
} ,
{
" text " : " data " ,
" kind " : " parameterName "
} ,
{
" text " : " : " ,
" kind " : " punctuation "
} ,
{
" text " : " " ,
" kind " : " space "
} ,
{
" text " : " any " ,
" kind " : " keyword "
} ,
{
" text " : " [ " ,
" kind " : " punctuation "
} ,
{
" text " : " ] " ,
" kind " : " punctuation "
} ,
{
" text " : " ) " ,
" kind " : " punctuation "
} ,
{
" text " : " : " ,
" kind " : " punctuation "
} ,
{
" text " : " " ,
" kind " : " space "
} ,
{
" text " : " void " ,
" kind " : " keyword "
}
] ,
" documentation " : [ ]
} )
) ;
}
2022-07-11 23:35:18 +00:00
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_completions_fmt ( ) {
2023-08-17 14:46:11 +00:00
let fixture_a = r #"
console . log ( someLongVaria )
" #;
let fixture_b = r #"
export const someLongVariable = 1
" #;
let line_index = LineIndex ::new ( fixture_a ) ;
let position = line_index
. offset_tsc ( lsp ::Position {
line : 1 ,
character : 33 ,
} )
. unwrap ( ) ;
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , _ ) = setup (
2023-08-17 14:46:11 +00:00
& temp_dir ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
& [
( " file:///a.ts " , fixture_a , 1 , LanguageId ::TypeScript ) ,
( " file:///b.ts " , fixture_b , 1 , LanguageId ::TypeScript ) ,
] ,
2023-10-02 06:32:05 +00:00
)
. await ;
2023-08-17 14:46:11 +00:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
let fmt_options_config = FmtOptionsConfig {
semi_colons : Some ( false ) ,
2023-09-18 19:48:32 +00:00
single_quote : Some ( true ) ,
2023-08-17 14:46:11 +00:00
.. Default ::default ( )
} ;
2023-10-02 06:32:05 +00:00
let info = ts_server
. get_completions (
snapshot . clone ( ) ,
2023-08-17 14:46:11 +00:00
specifier . clone ( ) ,
position ,
GetCompletionsAtPositionOptions {
user_preferences : UserPreferences {
2023-09-18 19:48:32 +00:00
quote_preference : Some ( ( & fmt_options_config ) . into ( ) ) ,
2023-08-17 14:46:11 +00:00
include_completions_for_module_exports : Some ( true ) ,
include_completions_with_insert_text : Some ( true ) ,
.. Default ::default ( )
} ,
.. Default ::default ( )
} ,
2023-10-02 06:32:05 +00:00
FormatCodeSettings ::from ( & fmt_options_config ) ,
)
. await
. unwrap ( ) ;
2023-08-17 14:46:11 +00:00
let entry = info
. entries
. iter ( )
. find ( | e | & e . name = = " someLongVariable " )
. unwrap ( ) ;
2023-10-02 06:32:05 +00:00
let details = ts_server
. get_completion_details (
snapshot . clone ( ) ,
GetCompletionDetailsArgs {
specifier ,
position ,
name : entry . name . clone ( ) ,
format_code_settings : Some ( FormatCodeSettings ::from (
& fmt_options_config ,
) ) ,
source : entry . source . clone ( ) ,
preferences : Some ( UserPreferences {
quote_preference : Some ( ( & fmt_options_config ) . into ( ) ) ,
.. Default ::default ( )
} ) ,
data : entry . data . clone ( ) ,
} ,
)
. await
. unwrap ( )
. unwrap ( ) ;
2023-08-17 14:46:11 +00:00
let actions = details . code_actions . unwrap ( ) ;
let action = actions
. iter ( )
. find ( | a | & a . description = = r # "Add import from "./b.ts""# )
. unwrap ( ) ;
let changes = action . changes . first ( ) . unwrap ( ) ;
let change = changes . text_changes . first ( ) . unwrap ( ) ;
assert_eq! (
change . new_text ,
2023-09-18 19:48:32 +00:00
" import { someLongVariable } from './b.ts' \n "
2023-08-17 14:46:11 +00:00
) ;
}
2023-10-02 06:32:05 +00:00
#[ tokio::test ]
async fn test_get_edits_for_file_rename ( ) {
2023-08-26 00:50:47 +00:00
let temp_dir = TempDir ::new ( ) ;
2023-10-02 06:32:05 +00:00
let ( ts_server , snapshot , _ ) = setup (
2023-08-26 00:50:47 +00:00
& temp_dir ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
& [
(
" file:///a.ts " ,
r # "import "./b.ts";"# ,
1 ,
LanguageId ::TypeScript ,
) ,
( " file:///b.ts " , r # ""# , 1 , LanguageId ::TypeScript ) ,
] ,
2023-10-02 06:32:05 +00:00
)
. await ;
let changes = ts_server
. get_edits_for_file_rename (
snapshot ,
2023-08-26 00:50:47 +00:00
resolve_url ( " file:///b.ts " ) . unwrap ( ) ,
2024-03-27 15:58:18 +00:00
resolve_url ( " file:///🦕.ts " ) . unwrap ( ) ,
2023-10-02 06:32:05 +00:00
FormatCodeSettings ::default ( ) ,
UserPreferences ::default ( ) ,
)
. await
. unwrap ( ) ;
2023-08-26 00:50:47 +00:00
assert_eq! (
changes ,
vec! [ FileTextChanges {
file_name : " file:///a.ts " . to_string ( ) ,
text_changes : vec ! [ TextChange {
span : TextSpan {
start : 8 ,
length : 6 ,
} ,
2024-03-27 15:58:18 +00:00
new_text : " ./🦕.ts " . to_string ( ) ,
2023-08-26 00:50:47 +00:00
} ] ,
is_new_file : None ,
} ]
) ;
}
2022-10-28 18:48:14 +00:00
#[ test ]
2023-09-26 02:54:07 +00:00
fn include_suppress_inlay_hint_settings ( ) {
2022-10-28 18:48:14 +00:00
let mut settings = WorkspaceSettings ::default ( ) ;
settings
2023-09-21 05:46:39 +00:00
. typescript
2022-10-28 18:48:14 +00:00
. inlay_hints
. parameter_names
. suppress_when_argument_matches_name = true ;
settings
2023-09-21 05:46:39 +00:00
. typescript
2022-10-28 18:48:14 +00:00
. inlay_hints
. variable_types
. suppress_when_type_matches_name = true ;
2024-03-26 15:52:20 +00:00
let mut config = config ::Config ::default ( ) ;
2024-03-21 04:29:52 +00:00
config . set_workspace_settings ( settings , vec! [ ] ) ;
2023-09-26 02:54:07 +00:00
let user_preferences = UserPreferences ::from_config_for_specifier (
& config ,
& ModuleSpecifier ::parse ( " file:///foo.ts " ) . unwrap ( ) ,
) ;
2022-10-28 18:48:14 +00:00
assert_eq! (
user_preferences . include_inlay_variable_type_hints_when_type_matches_name ,
Some ( false )
) ;
assert_eq! (
user_preferences
. include_inlay_parameter_name_hints_when_argument_matches_name ,
Some ( false )
) ;
}
2024-04-11 20:55:27 +00:00
#[ tokio::test ]
2024-04-14 21:42:58 +00:00
async fn resolve_unknown_dependency ( ) {
2024-04-11 20:55:27 +00:00
let temp_dir = TempDir ::new ( ) ;
let ( _ , snapshot , _ ) = setup (
& temp_dir ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
& [ ( " file:///a.ts " , " " , 1 , LanguageId ::TypeScript ) ] ,
)
. await ;
let mut state = setup_op_state ( snapshot ) ;
2024-04-12 19:45:38 +00:00
let resolved = op_resolve_inner (
2024-04-11 20:55:27 +00:00
& mut state ,
ResolveArgs {
base : " file:///a.ts " . to_string ( ) ,
specifiers : vec ! [ " ./b.ts " . to_string ( ) ] ,
} ,
)
. unwrap ( ) ;
assert_eq! (
resolved ,
vec! [ Some ( (
2024-04-14 21:42:58 +00:00
" file:///b.ts " . to_string ( ) ,
MediaType ::TypeScript . as_ts_extension ( ) . to_string ( )
2024-04-11 20:55:27 +00:00
) ) ]
) ;
}
2024-04-23 22:29:29 +00:00
#[ test ]
fn coalesce_pending_change ( ) {
use ChangeKind ::* ;
fn change < S : AsRef < str > > (
project_version : usize ,
scripts : impl IntoIterator < Item = ( S , ChangeKind ) > ,
config_changed : bool ,
) -> PendingChange {
PendingChange {
project_version ,
modified_scripts : scripts
. into_iter ( )
. map ( | ( s , c ) | ( s . as_ref ( ) . into ( ) , c ) )
. collect ( ) ,
config_changed ,
}
}
let cases = [
(
// start
change ( 1 , [ ( " file:///a.ts " , Closed ) ] , false ) ,
// new
change ( 2 , Some ( ( " file:///b.ts " , Opened ) ) , false ) ,
// expected
change (
2 ,
[ ( " file:///a.ts " , Closed ) , ( " file:///b.ts " , Opened ) ] ,
false ,
) ,
) ,
(
// start
change (
1 ,
[ ( " file:///a.ts " , Closed ) , ( " file:///b.ts " , Opened ) ] ,
false ,
) ,
// new
change (
2 ,
// a gets closed then reopened, b gets opened then closed
[ ( " file:///a.ts " , Opened ) , ( " file:///b.ts " , Closed ) ] ,
false ,
) ,
// expected
change (
2 ,
[ ( " file:///a.ts " , Opened ) , ( " file:///b.ts " , Closed ) ] ,
false ,
) ,
) ,
(
change (
1 ,
[ ( " file:///a.ts " , Opened ) , ( " file:///b.ts " , Modified ) ] ,
false ,
) ,
// new
change (
2 ,
// a gets opened then modified, b gets modified then closed
[ ( " file:///a.ts " , Opened ) , ( " file:///b.ts " , Closed ) ] ,
false ,
) ,
// expected
change (
2 ,
[ ( " file:///a.ts " , Opened ) , ( " file:///b.ts " , Closed ) ] ,
false ,
) ,
) ,
] ;
for ( start , new , expected ) in cases {
let mut pending = start ;
pending . coalesce ( new . project_version , new . modified_scripts , false ) ;
assert_eq! ( pending , expected ) ;
}
}
2020-12-07 10:46:39 +00:00
}