2021-01-11 12:13:41 -05:00
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
2020-12-07 05:46:39 -05:00
2021-01-31 22:30:41 -05:00
use super ::analysis ::CodeLensSource ;
2020-12-24 05:53:03 -05:00
use super ::analysis ::ResolvedDependency ;
2021-02-09 17:46:12 -05:00
use super ::analysis ::ResolvedDependencyErr ;
2021-02-06 07:39:01 -05:00
use super ::language_server ;
2020-12-21 08:44:26 -05:00
use super ::language_server ::StateSnapshot ;
2020-12-07 05:46:39 -05:00
use super ::text ;
2021-01-22 05:03:16 -05:00
use super ::text ::LineIndex ;
2020-12-07 05:46:39 -05:00
use crate ::media_type ::MediaType ;
2020-12-21 08:44:26 -05:00
use crate ::tokio_util ::create_basic_runtime ;
2020-12-15 14:34:39 -05:00
use crate ::tsc ;
2020-12-07 05:46:39 -05:00
use crate ::tsc ::ResolveArgs ;
use crate ::tsc_config ::TsConfig ;
2020-12-21 08:44:26 -05:00
use deno_core ::error ::anyhow ;
2020-12-07 05:46:39 -05:00
use deno_core ::error ::custom_error ;
use deno_core ::error ::AnyError ;
use deno_core ::json_op_sync ;
2021-02-17 13:47:18 -05:00
use deno_core ::resolve_url ;
2021-02-24 22:15:55 -05:00
use deno_core ::serde ::de ;
2020-12-07 05:46:39 -05:00
use deno_core ::serde ::Deserialize ;
2020-12-08 05:36:13 -05:00
use deno_core ::serde ::Serialize ;
2020-12-07 05:46:39 -05:00
use deno_core ::serde_json ;
use deno_core ::serde_json ::json ;
use deno_core ::serde_json ::Value ;
2020-12-29 19:58:20 -05:00
use deno_core ::url ::Url ;
2020-12-07 05:46:39 -05:00
use deno_core ::JsRuntime ;
use deno_core ::ModuleSpecifier ;
use deno_core ::OpFn ;
use deno_core ::RuntimeOptions ;
2021-01-29 14:34:33 -05:00
use lspower ::lsp ;
2020-12-07 05:46:39 -05:00
use regex ::Captures ;
use regex ::Regex ;
use std ::borrow ::Cow ;
use std ::collections ::HashMap ;
2020-12-21 08:44:26 -05:00
use std ::thread ;
2021-01-22 05:03:16 -05:00
use text_size ::TextSize ;
2020-12-21 08:44:26 -05:00
use tokio ::sync ::mpsc ;
use tokio ::sync ::oneshot ;
type Request = (
RequestMethod ,
StateSnapshot ,
oneshot ::Sender < Result < Value , AnyError > > ,
) ;
#[ derive(Clone, Debug) ]
pub struct TsServer ( mpsc ::UnboundedSender < Request > ) ;
impl TsServer {
pub fn new ( ) -> Self {
let ( tx , mut rx ) = mpsc ::unbounded_channel ::< Request > ( ) ;
let _join_handle = thread ::spawn ( move | | {
// TODO(@kitsonk) we need to allow displaying diagnostics here, but the
// current compiler snapshot sends them to stdio which would totally break
// the language server...
let mut ts_runtime = start ( false ) . expect ( " could not start tsc " ) ;
2021-01-12 02:50:02 -05:00
let runtime = create_basic_runtime ( ) ;
2020-12-21 08:44:26 -05:00
runtime . block_on ( async {
while let Some ( ( req , state_snapshot , tx ) ) = rx . recv ( ) . await {
let value = request ( & mut ts_runtime , state_snapshot , req ) ;
if tx . send ( value ) . is_err ( ) {
warn! ( " Unable to send result to client. " ) ;
}
}
} )
} ) ;
Self ( tx )
}
2021-02-24 22:15:55 -05:00
pub async fn request < R > (
2020-12-21 08:44:26 -05:00
& self ,
snapshot : StateSnapshot ,
req : RequestMethod ,
2021-02-24 22:15:55 -05:00
) -> Result < R , AnyError >
where
R : de ::DeserializeOwned ,
{
2020-12-21 08:44:26 -05:00
let ( tx , rx ) = oneshot ::channel ::< Result < Value , AnyError > > ( ) ;
if self . 0. send ( ( req , snapshot , tx ) ) . is_err ( ) {
return Err ( anyhow! ( " failed to send request to tsc thread " ) ) ;
}
2021-02-24 22:15:55 -05:00
rx . await ? . map ( | v | serde_json ::from_value ::< R > ( v ) . unwrap ( ) )
2020-12-21 08:44:26 -05:00
}
}
2020-12-07 05:46:39 -05:00
2021-01-22 05:03:16 -05: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.
#[ derive(Debug, Clone) ]
pub struct AssetDocument {
pub text : String ,
2021-02-12 06:49:42 -05:00
pub length : usize ,
2021-01-22 05:03:16 -05:00
pub line_index : LineIndex ,
}
2021-02-12 06:49:42 -05:00
impl AssetDocument {
pub fn new < T : AsRef < str > > ( text : T ) -> Self {
let text = text . as_ref ( ) ;
Self {
text : text . to_string ( ) ,
length : text . encode_utf16 ( ) . count ( ) ,
line_index : LineIndex ::new ( text ) ,
}
}
}
#[ derive(Debug, Clone) ]
pub struct Assets ( HashMap < ModuleSpecifier , Option < AssetDocument > > ) ;
impl Default for Assets {
fn default ( ) -> Self {
let assets = tsc ::STATIC_ASSETS
. iter ( )
. map ( | ( k , v ) | {
let url_str = format! ( " asset:/// {} " , k ) ;
2021-02-17 13:47:18 -05:00
let specifier = resolve_url ( & url_str ) . unwrap ( ) ;
2021-02-12 06:49:42 -05:00
let asset = AssetDocument ::new ( v ) ;
( specifier , Some ( asset ) )
} )
. collect ( ) ;
Self ( assets )
}
}
impl Assets {
pub fn contains_key ( & self , k : & ModuleSpecifier ) -> bool {
self . 0. contains_key ( k )
}
pub fn get ( & self , k : & ModuleSpecifier ) -> Option < & Option < AssetDocument > > {
self . 0. get ( k )
}
pub fn insert (
& mut self ,
k : ModuleSpecifier ,
v : Option < AssetDocument > ,
) -> Option < Option < AssetDocument > > {
self . 0. insert ( k , v )
}
}
2020-12-15 14:34:39 -05:00
/// Optionally returns an internal asset, first checking for any static assets
/// in Rust, then checking any previously retrieved static assets from the
/// isolate, and then finally, the tsc isolate itself.
2020-12-21 08:44:26 -05:00
pub async fn get_asset (
2020-12-15 14:34:39 -05:00
specifier : & ModuleSpecifier ,
2020-12-21 08:44:26 -05:00
ts_server : & TsServer ,
2021-02-12 06:49:42 -05:00
state_snapshot : StateSnapshot ,
2021-01-22 05:03:16 -05:00
) -> Result < Option < AssetDocument > , AnyError > {
2020-12-15 14:34:39 -05:00
let specifier_str = specifier . to_string ( ) . replace ( " asset:/// " , " " ) ;
2021-01-22 05:03:16 -05:00
if let Some ( text ) = tsc ::get_asset ( & specifier_str ) {
2021-02-12 06:49:42 -05:00
let maybe_asset = Some ( AssetDocument ::new ( text ) ) ;
2021-01-22 05:03:16 -05:00
Ok ( maybe_asset )
2020-12-15 14:34:39 -05:00
} else {
2021-01-22 05:03:16 -05:00
let res = ts_server
2021-02-12 06:49:42 -05:00
. request ( state_snapshot , RequestMethod ::GetAsset ( specifier . clone ( ) ) )
2021-01-22 05:03:16 -05:00
. await ? ;
let maybe_text : Option < String > = serde_json ::from_value ( res ) ? ;
let maybe_asset = if let Some ( text ) = maybe_text {
2021-02-12 06:49:42 -05:00
Some ( AssetDocument ::new ( text ) )
2021-01-22 05:03:16 -05:00
} else {
None
} ;
Ok ( maybe_asset )
2020-12-07 05:46:39 -05:00
}
}
2021-02-15 21:34:09 -05:00
fn display_parts_to_string ( parts : Vec < SymbolDisplayPart > ) -> String {
parts
. into_iter ( )
. map ( | p | p . text )
. collect ::< Vec < String > > ( )
. join ( " " )
2020-12-07 05:46:39 -05:00
}
fn get_tag_body_text ( tag : & JSDocTagInfo ) -> Option < String > {
tag . text . as_ref ( ) . map ( | text | match tag . name . as_str ( ) {
" example " = > {
let caption_regex =
Regex ::new ( r "<caption>(.*?)</caption>\s*\r?\n((?:\s|\S)*)" ) . unwrap ( ) ;
if caption_regex . is_match ( & text ) {
caption_regex
. replace ( text , | c : & Captures | {
format! ( " {} \n \n {} " , & c [ 1 ] , make_codeblock ( & c [ 2 ] ) )
} )
. to_string ( )
} else {
make_codeblock ( text )
}
}
" author " = > {
let email_match_regex = Regex ::new ( r "(.+)\s<([-.\w]+@[-.\w]+)>" ) . unwrap ( ) ;
email_match_regex
. replace ( text , | c : & Captures | format! ( " {} {} " , & c [ 1 ] , & c [ 2 ] ) )
. to_string ( )
}
" default " = > make_codeblock ( text ) ,
_ = > replace_links ( text ) ,
} )
}
fn get_tag_documentation ( tag : & JSDocTagInfo ) -> String {
match tag . name . as_str ( ) {
" augments " | " extends " | " param " | " template " = > {
if let Some ( text ) = & tag . text {
let part_regex = Regex ::new ( r "^(\S+)\s*-?\s*" ) . unwrap ( ) ;
let body : Vec < & str > = part_regex . split ( & text ) . collect ( ) ;
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 ) ;
let maybe_text = get_tag_body_text ( tag ) ;
if let Some ( text ) = maybe_text {
if text . contains ( '\n' ) {
format! ( " {} \n {} " , label , text )
} else {
format! ( " {} - {} " , label , text )
}
} else {
label
}
}
fn make_codeblock ( text : & str ) -> String {
let codeblock_regex = Regex ::new ( r "^\s*[~`]{3}" ) . unwrap ( ) ;
if codeblock_regex . is_match ( text ) {
text . to_string ( )
} else {
format! ( " ``` \n {} \n ``` " , text )
}
}
/// Replace JSDoc like links (`{@link http://example.com}`) with markdown links
fn replace_links ( text : & str ) -> String {
let jsdoc_links_regex = Regex ::new ( r "(?i)\{@(link|linkplain|linkcode) (https?://[^ |}]+?)(?:[| ]([^{}\n]+?))?\}" ) . unwrap ( ) ;
jsdoc_links_regex
. replace_all ( text , | c : & Captures | match & c [ 1 ] {
" 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-01-31 22:30:41 -05:00
#[ derive(Debug, Clone, Deserialize, PartialEq, Eq) ]
2020-12-07 05:46:39 -05: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-01-29 14:34:33 -05:00
impl From < ScriptElementKind > for lsp ::CompletionItemKind {
2020-12-08 05:36:13 -05:00
fn from ( kind : ScriptElementKind ) -> Self {
2021-01-29 14:34:33 -05:00
use lspower ::lsp ::CompletionItemKind ;
2020-12-08 05:36:13 -05:00
match kind {
ScriptElementKind ::PrimitiveType | ScriptElementKind ::Keyword = > {
CompletionItemKind ::Keyword
}
ScriptElementKind ::ConstElement = > CompletionItemKind ::Constant ,
ScriptElementKind ::LetElement
| ScriptElementKind ::VariableElement
| ScriptElementKind ::LocalVariableElement
| ScriptElementKind ::Alias = > CompletionItemKind ::Variable ,
ScriptElementKind ::MemberVariableElement
| ScriptElementKind ::MemberGetAccessorElement
| ScriptElementKind ::MemberSetAccessorElement = > {
CompletionItemKind ::Field
}
ScriptElementKind ::FunctionElement = > CompletionItemKind ::Function ,
ScriptElementKind ::MemberFunctionElement
| ScriptElementKind ::ConstructSignatureElement
| ScriptElementKind ::CallSignatureElement
| ScriptElementKind ::IndexSignatureElement = > CompletionItemKind ::Method ,
ScriptElementKind ::EnumElement = > CompletionItemKind ::Enum ,
ScriptElementKind ::ModuleElement
| ScriptElementKind ::ExternalModuleName = > CompletionItemKind ::Module ,
ScriptElementKind ::ClassElement | ScriptElementKind ::TypeElement = > {
CompletionItemKind ::Class
}
ScriptElementKind ::InterfaceElement = > CompletionItemKind ::Interface ,
ScriptElementKind ::Warning | ScriptElementKind ::ScriptElement = > {
CompletionItemKind ::File
}
ScriptElementKind ::Directory = > CompletionItemKind ::Folder ,
ScriptElementKind ::String = > CompletionItemKind ::Constant ,
_ = > CompletionItemKind ::Property ,
}
}
}
2021-02-04 13:53:02 -05:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq) ]
2020-12-07 05:46:39 -05:00
#[ serde(rename_all = " camelCase " ) ]
pub struct TextSpan {
2021-01-31 22:30:41 -05:00
pub start : u32 ,
pub length : u32 ,
2020-12-07 05:46:39 -05:00
}
impl TextSpan {
2021-01-29 14:34:33 -05:00
pub fn to_range ( & self , line_index : & LineIndex ) -> lsp ::Range {
lsp ::Range {
2021-01-22 05:03:16 -05:00
start : line_index . position_tsc ( self . start . into ( ) ) ,
end : line_index . position_tsc ( TextSize ::from ( self . start + self . length ) ) ,
2020-12-07 05:46:39 -05:00
}
}
}
#[ derive(Debug, Deserialize, Clone) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct SymbolDisplayPart {
text : String ,
kind : String ,
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct JSDocTagInfo {
name : String ,
text : Option < String > ,
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct QuickInfo {
kind : ScriptElementKind ,
kind_modifiers : String ,
text_span : TextSpan ,
display_parts : Option < Vec < SymbolDisplayPart > > ,
documentation : Option < Vec < SymbolDisplayPart > > ,
tags : Option < Vec < JSDocTagInfo > > ,
}
impl QuickInfo {
2021-01-29 14:34:33 -05:00
pub fn to_hover ( & self , line_index : & LineIndex ) -> lsp ::Hover {
let mut contents = Vec ::< lsp ::MarkedString > ::new ( ) ;
2020-12-07 05:46:39 -05:00
if let Some ( display_string ) =
2021-02-15 21:34:09 -05:00
self . display_parts . clone ( ) . map ( display_parts_to_string )
2020-12-07 05:46:39 -05:00
{
2021-01-29 14:34:33 -05:00
contents . push ( lsp ::MarkedString ::from_language_code (
2020-12-07 05:46:39 -05:00
" typescript " . to_string ( ) ,
display_string ,
) ) ;
}
if let Some ( documentation ) =
2021-02-15 21:34:09 -05:00
self . documentation . clone ( ) . map ( display_parts_to_string )
2020-12-07 05:46:39 -05:00
{
2021-01-29 14:34:33 -05:00
contents . push ( lsp ::MarkedString ::from_markdown ( documentation ) ) ;
2020-12-07 05:46:39 -05:00
}
if let Some ( tags ) = & self . tags {
let tags_preview = tags
. iter ( )
. map ( get_tag_documentation )
. collect ::< Vec < String > > ( )
. join ( " \n \n " ) ;
if ! tags_preview . is_empty ( ) {
2021-01-29 14:34:33 -05:00
contents . push ( lsp ::MarkedString ::from_markdown ( format! (
2020-12-07 05:46:39 -05:00
" \n \n {} " ,
tags_preview
) ) ) ;
}
}
2021-01-29 14:34:33 -05:00
lsp ::Hover {
contents : lsp ::HoverContents ::Array ( contents ) ,
2020-12-07 05:46:39 -05:00
range : Some ( self . text_span . to_range ( line_index ) ) ,
}
}
}
2021-01-12 16:53:27 -05:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct DocumentSpan {
text_span : TextSpan ,
pub file_name : String ,
original_text_span : Option < TextSpan > ,
original_file_name : Option < String > ,
context_span : Option < TextSpan > ,
original_context_span : Option < TextSpan > ,
}
impl DocumentSpan {
2021-02-06 07:39:01 -05:00
pub ( crate ) async fn to_link (
2021-01-12 16:53:27 -05:00
& self ,
2021-01-22 05:03:16 -05:00
line_index : & LineIndex ,
2021-02-06 07:39:01 -05:00
language_server : & mut language_server ::Inner ,
) -> Option < lsp ::LocationLink > {
2021-02-17 13:47:18 -05:00
let target_specifier = resolve_url ( & self . file_name ) . unwrap ( ) ;
2021-02-17 23:37:05 -05:00
let target_line_index = language_server
. get_line_index ( target_specifier . clone ( ) )
. await
. ok ( ) ? ;
let target_uri = language_server
. url_map
. normalize_specifier ( & target_specifier )
. unwrap ( ) ;
let ( target_range , target_selection_range ) =
if let Some ( context_span ) = & self . context_span {
(
context_span . to_range ( & target_line_index ) ,
self . text_span . to_range ( & target_line_index ) ,
)
} else {
(
self . text_span . to_range ( & target_line_index ) ,
self . text_span . to_range ( & target_line_index ) ,
)
2021-01-12 16:53:27 -05:00
} ;
2021-02-17 23:37:05 -05:00
let origin_selection_range =
if let Some ( original_context_span ) = & self . original_context_span {
Some ( original_context_span . to_range ( line_index ) )
} else if let Some ( original_text_span ) = & self . original_text_span {
Some ( original_text_span . to_range ( line_index ) )
} else {
None
} ;
let link = lsp ::LocationLink {
origin_selection_range ,
target_uri ,
target_range ,
target_selection_range ,
} ;
Some ( link )
2021-01-12 16:53:27 -05:00
}
}
2021-01-31 22:30:41 -05: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 ,
line_index : & LineIndex ,
specifier : & ModuleSpecifier ,
source : & CodeLensSource ,
) -> lsp ::CodeLens {
2021-02-08 05:45:10 -05: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-01-31 22:30:41 -05:00
lsp ::CodeLens {
2021-02-08 05:45:10 -05:00
range ,
2021-01-31 22:30:41 -05:00
command : None ,
data : Some ( json! ( {
" specifier " : specifier ,
" source " : source
} ) ) ,
}
}
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 16:53:27 -05:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ImplementationLocation {
#[ serde(flatten) ]
pub document_span : DocumentSpan ,
// ImplementationLocation props
kind : ScriptElementKind ,
display_parts : Vec < SymbolDisplayPart > ,
}
2021-02-08 05:45:10 -05:00
impl ImplementationLocation {
2021-02-17 23:37:05 -05:00
pub ( crate ) fn to_location (
& self ,
line_index : & LineIndex ,
language_server : & mut language_server ::Inner ,
) -> lsp ::Location {
let specifier = resolve_url ( & self . document_span . file_name ) . unwrap ( ) ;
let uri = language_server
. url_map
. normalize_specifier ( & specifier )
. unwrap ( ) ;
2021-02-08 05:45:10 -05:00
lsp ::Location {
uri ,
range : self . document_span . text_span . to_range ( line_index ) ,
}
}
2021-02-17 22:15:13 -05:00
pub ( crate ) async fn to_link (
& self ,
line_index : & LineIndex ,
language_server : & mut language_server ::Inner ,
) -> Option < lsp ::LocationLink > {
self
. document_span
. to_link ( line_index , language_server )
. await
}
2021-02-08 05:45:10 -05:00
}
2020-12-29 19:58:20 -05:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct RenameLocation {
2021-01-16 07:00:42 -05:00
#[ serde(flatten) ]
document_span : DocumentSpan ,
2020-12-29 19:58:20 -05:00
// RenameLocation props
prefix_text : Option < String > ,
suffix_text : Option < String > ,
}
pub struct RenameLocations {
pub locations : Vec < RenameLocation > ,
}
impl RenameLocations {
2021-02-06 07:39:01 -05:00
pub ( crate ) async fn into_workspace_edit (
2020-12-29 19:58:20 -05:00
self ,
new_name : & str ,
2021-02-06 07:39:01 -05:00
language_server : & mut language_server ::Inner ,
) -> Result < lsp ::WorkspaceEdit , AnyError > {
2021-01-29 14:34:33 -05:00
let mut text_document_edit_map : HashMap < Url , lsp ::TextDocumentEdit > =
2020-12-29 19:58:20 -05:00
HashMap ::new ( ) ;
for location in self . locations . iter ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url ( & location . document_span . file_name ) ? ;
2021-02-17 23:37:05 -05:00
let uri = language_server . url_map . normalize_specifier ( & specifier ) ? ;
2020-12-29 19:58:20 -05: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 14:34:33 -05:00
lsp ::TextDocumentEdit {
text_document : lsp ::OptionalVersionedTextDocumentIdentifier {
2020-12-29 19:58:20 -05:00
uri : uri . clone ( ) ,
2021-02-06 07:39:01 -05:00
version : language_server . document_version ( specifier . clone ( ) ) ,
2020-12-29 19:58:20 -05:00
} ,
2021-01-29 14:34:33 -05:00
edits :
Vec ::< lsp ::OneOf < lsp ::TextEdit , lsp ::AnnotatedTextEdit > > ::new ( ) ,
2020-12-29 19:58:20 -05:00
} ,
) ;
}
// push TextEdit for ensured `TextDocumentEdit.edits`.
let document_edit = text_document_edit_map . get_mut ( & uri ) . unwrap ( ) ;
2021-01-29 14:34:33 -05:00
document_edit . edits . push ( lsp ::OneOf ::Left ( lsp ::TextEdit {
range : location
. document_span
. text_span
2021-02-06 07:39:01 -05:00
. to_range ( & language_server . get_line_index ( specifier . clone ( ) ) . await ? ) ,
2021-01-29 14:34:33 -05:00
new_text : new_name . to_string ( ) ,
} ) ) ;
2020-12-29 19:58:20 -05:00
}
2021-01-29 14:34:33 -05:00
Ok ( lsp ::WorkspaceEdit {
2021-01-12 02:50:02 -05:00
change_annotations : None ,
2020-12-29 19:58:20 -05:00
changes : None ,
2021-01-29 14:34:33 -05:00
document_changes : Some ( lsp ::DocumentChanges ::Edits (
2020-12-29 19:58:20 -05:00
text_document_edit_map . values ( ) . cloned ( ) . collect ( ) ,
) ) ,
} )
}
}
2020-12-07 05:46:39 -05: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 {
file_name : Option < String > ,
is_in_string : Option < bool > ,
text_span : TextSpan ,
context_span : Option < TextSpan > ,
kind : HighlightSpanKind ,
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct DefinitionInfo {
kind : ScriptElementKind ,
name : String ,
container_kind : Option < ScriptElementKind > ,
container_name : Option < String > ,
2021-01-12 16:53:27 -05:00
#[ serde(flatten) ]
pub document_span : DocumentSpan ,
2020-12-07 05:46:39 -05:00
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct DefinitionInfoAndBoundSpan {
pub definitions : Option < Vec < DefinitionInfo > > ,
text_span : TextSpan ,
}
impl DefinitionInfoAndBoundSpan {
2021-02-06 07:39:01 -05:00
pub ( crate ) async fn to_definition (
2020-12-07 05:46:39 -05:00
& self ,
2021-01-22 05:03:16 -05:00
line_index : & LineIndex ,
2021-02-06 07:39:01 -05:00
language_server : & mut language_server ::Inner ,
) -> Option < lsp ::GotoDefinitionResponse > {
2020-12-07 05:46:39 -05:00
if let Some ( definitions ) = & self . definitions {
2021-01-29 14:34:33 -05:00
let mut location_links = Vec ::< lsp ::LocationLink > ::new ( ) ;
2020-12-21 08:44:26 -05:00
for di in definitions {
2021-02-06 07:39:01 -05:00
if let Some ( link ) =
di . document_span . to_link ( line_index , language_server ) . await
2021-01-12 16:53:27 -05:00
{
location_links . push ( link ) ;
2020-12-21 08:44:26 -05:00
}
}
2021-01-29 14:34:33 -05:00
Some ( lsp ::GotoDefinitionResponse ::Link ( location_links ) )
2020-12-07 05:46:39 -05:00
} else {
None
}
}
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct DocumentHighlights {
file_name : String ,
highlight_spans : Vec < HighlightSpan > ,
}
impl DocumentHighlights {
pub fn to_highlight (
& self ,
2021-01-22 05:03:16 -05:00
line_index : & LineIndex ,
2021-01-29 14:34:33 -05:00
) -> Vec < lsp ::DocumentHighlight > {
2020-12-07 05:46:39 -05:00
self
. highlight_spans
. iter ( )
2021-01-29 14:34:33 -05:00
. map ( | hs | lsp ::DocumentHighlight {
2020-12-07 05:46:39 -05:00
range : hs . text_span . to_range ( line_index ) ,
kind : match hs . kind {
HighlightSpanKind ::WrittenReference = > {
2021-01-29 14:34:33 -05:00
Some ( lsp ::DocumentHighlightKind ::Write )
2020-12-07 05:46:39 -05:00
}
2021-01-29 14:34:33 -05:00
_ = > Some ( lsp ::DocumentHighlightKind ::Read ) ,
2020-12-07 05:46:39 -05:00
} ,
} )
. collect ( )
}
}
2021-02-04 13:53:02 -05:00
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct TextChange {
span : TextSpan ,
new_text : String ,
}
impl TextChange {
pub fn as_text_edit (
& self ,
line_index : & LineIndex ,
) -> 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 {
file_name : String ,
text_changes : Vec < TextChange > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
is_new_file : Option < bool > ,
}
impl FileTextChanges {
2021-02-06 07:39:01 -05:00
pub ( crate ) async fn to_text_document_edit (
2021-02-04 13:53:02 -05:00
& self ,
2021-02-06 07:39:01 -05:00
language_server : & mut language_server ::Inner ,
) -> Result < lsp ::TextDocumentEdit , AnyError > {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url ( & self . file_name ) ? ;
2021-02-06 07:39:01 -05:00
let line_index = language_server . get_line_index ( specifier . clone ( ) ) . await ? ;
2021-02-04 13:53:02 -05:00
let edits = self
. text_changes
. iter ( )
. map ( | tc | tc . as_text_edit ( & line_index ) )
. collect ( ) ;
Ok ( lsp ::TextDocumentEdit {
text_document : lsp ::OptionalVersionedTextDocumentIdentifier {
2021-02-17 13:47:18 -05:00
uri : specifier . clone ( ) ,
2021-02-06 07:39:01 -05:00
version : language_server . document_version ( specifier ) ,
2021-02-04 13:53:02 -05:00
} ,
edits ,
} )
}
}
#[ 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 > ,
}
#[ derive(Debug, Clone, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CombinedCodeActions {
pub changes : Vec < FileTextChanges > ,
pub commands : Option < Vec < Value > > ,
}
2020-12-07 05:46:39 -05:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ReferenceEntry {
is_write_access : bool ,
pub is_definition : bool ,
is_in_string : Option < bool > ,
2021-01-16 07:00:42 -05:00
#[ serde(flatten) ]
pub document_span : DocumentSpan ,
2020-12-07 05:46:39 -05:00
}
impl ReferenceEntry {
2021-02-17 23:37:05 -05:00
pub ( crate ) fn to_location (
& self ,
line_index : & LineIndex ,
language_server : & mut language_server ::Inner ,
) -> lsp ::Location {
let specifier = resolve_url ( & self . document_span . file_name ) . unwrap ( ) ;
let uri = language_server
. url_map
. normalize_specifier ( & specifier )
. unwrap ( ) ;
2021-01-29 14:34:33 -05:00
lsp ::Location {
2020-12-07 05:46:39 -05:00
uri ,
2021-01-16 07:00:42 -05:00
range : self . document_span . text_span . to_range ( line_index ) ,
2020-12-07 05:46:39 -05:00
}
}
}
2020-12-08 05:36:13 -05:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CompletionInfo {
entries : Vec < CompletionEntry > ,
is_member_completion : bool ,
}
impl CompletionInfo {
pub fn into_completion_response (
self ,
2021-01-22 05:03:16 -05:00
line_index : & LineIndex ,
2021-01-29 14:34:33 -05:00
) -> lsp ::CompletionResponse {
2020-12-08 05:36:13 -05:00
let items = self
. entries
. into_iter ( )
. map ( | entry | entry . into_completion_item ( line_index ) )
. collect ( ) ;
2021-01-29 14:34:33 -05:00
lsp ::CompletionResponse ::Array ( items )
2020-12-08 05:36:13 -05:00
}
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CompletionEntry {
kind : ScriptElementKind ,
kind_modifiers : Option < String > ,
name : String ,
sort_text : String ,
insert_text : Option < String > ,
replacement_span : Option < TextSpan > ,
has_action : Option < bool > ,
source : Option < String > ,
is_recommended : Option < bool > ,
}
impl CompletionEntry {
pub fn into_completion_item (
self ,
2021-01-22 05:03:16 -05:00
line_index : & LineIndex ,
2021-01-29 14:34:33 -05:00
) -> lsp ::CompletionItem {
let mut item = lsp ::CompletionItem {
2020-12-08 05:36:13 -05:00
label : self . name ,
kind : Some ( self . kind . into ( ) ) ,
sort_text : Some ( self . sort_text . clone ( ) ) ,
// TODO(lucacasonato): missing commit_characters
.. Default ::default ( )
} ;
if let Some ( true ) = self . is_recommended {
// Make sure isRecommended property always comes first
// https://github.com/Microsoft/vscode/issues/40325
item . preselect = Some ( true ) ;
} else if self . source . is_some ( ) {
// De-prioritze auto-imports
// https://github.com/Microsoft/vscode/issues/40311
item . sort_text = Some ( " \u{ffff} " . to_string ( ) + & self . sort_text )
}
match item . kind {
2021-01-29 14:34:33 -05:00
Some ( lsp ::CompletionItemKind ::Function )
| Some ( lsp ::CompletionItemKind ::Method ) = > {
item . insert_text_format = Some ( lsp ::InsertTextFormat ::Snippet ) ;
2020-12-08 05:36:13 -05:00
}
_ = > { }
}
let mut insert_text = self . insert_text ;
2021-01-29 14:34:33 -05:00
let replacement_range : Option < lsp ::Range > =
2020-12-08 05:36:13 -05:00
self . replacement_span . map ( | span | span . to_range ( line_index ) ) ;
// TODO(lucacasonato): port other special cases from https://github.com/theia-ide/typescript-language-server/blob/fdf28313833cd6216d00eb4e04dc7f00f4c04f09/server/src/completion.ts#L49-L55
if let Some ( kind_modifiers ) = self . kind_modifiers {
if kind_modifiers . contains ( " \\ optional \\ " ) {
if insert_text . is_none ( ) {
insert_text = Some ( item . label . clone ( ) ) ;
}
if item . filter_text . is_none ( ) {
item . filter_text = Some ( item . label . clone ( ) ) ;
}
item . label + = " ? " ;
}
}
if let Some ( insert_text ) = insert_text {
if let Some ( replacement_range ) = replacement_range {
2021-01-29 14:34:33 -05:00
item . text_edit = Some ( lsp ::CompletionTextEdit ::Edit (
lsp ::TextEdit ::new ( replacement_range , insert_text ) ,
2020-12-08 05:36:13 -05:00
) ) ;
} else {
item . insert_text = Some ( insert_text ) ;
}
}
item
}
}
2021-02-15 21:34:09 -05:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct SignatureHelpItems {
items : Vec < SignatureHelpItem > ,
applicable_span : TextSpan ,
selected_item_index : u32 ,
argument_index : u32 ,
argument_count : u32 ,
}
impl SignatureHelpItems {
pub fn into_signature_help ( self ) -> lsp ::SignatureHelp {
lsp ::SignatureHelp {
signatures : self
. items
. into_iter ( )
. map ( | item | item . into_signature_information ( ) )
. collect ( ) ,
active_parameter : Some ( self . argument_index ) ,
active_signature : Some ( self . selected_item_index ) ,
}
}
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct SignatureHelpItem {
is_variadic : bool ,
prefix_display_parts : Vec < SymbolDisplayPart > ,
suffix_display_parts : Vec < SymbolDisplayPart > ,
separator_display_parts : Vec < SymbolDisplayPart > ,
parameters : Vec < SignatureHelpParameter > ,
documentation : Vec < SymbolDisplayPart > ,
tags : Vec < JSDocTagInfo > ,
}
impl SignatureHelpItem {
pub fn into_signature_information ( self ) -> lsp ::SignatureInformation {
let prefix_text = display_parts_to_string ( self . prefix_display_parts ) ;
let params_text = self
. parameters
. iter ( )
. map ( | param | display_parts_to_string ( param . display_parts . clone ( ) ) )
. collect ::< Vec < String > > ( )
. join ( " , " ) ;
let suffix_text = display_parts_to_string ( self . suffix_display_parts ) ;
lsp ::SignatureInformation {
label : format ! ( " {}{}{} " , prefix_text , params_text , suffix_text ) ,
documentation : Some ( lsp ::Documentation ::String ( display_parts_to_string (
self . documentation ,
) ) ) ,
parameters : Some (
self
. parameters
. into_iter ( )
. map ( | param | param . into_parameter_information ( ) )
. collect ( ) ,
) ,
active_parameter : None ,
}
}
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct SignatureHelpParameter {
name : String ,
documentation : Vec < SymbolDisplayPart > ,
display_parts : Vec < SymbolDisplayPart > ,
is_optional : bool ,
}
impl SignatureHelpParameter {
pub fn into_parameter_information ( self ) -> lsp ::ParameterInformation {
lsp ::ParameterInformation {
label : lsp ::ParameterLabel ::Simple ( display_parts_to_string (
self . display_parts ,
) ) ,
documentation : Some ( lsp ::Documentation ::String ( display_parts_to_string (
self . documentation ,
) ) ) ,
}
}
}
2020-12-07 05:46:39 -05:00
#[ derive(Debug, Clone, Deserialize) ]
struct Response {
id : usize ,
data : Value ,
}
struct State < ' a > {
2020-12-15 14:34:39 -05:00
asset : Option < String > ,
2020-12-07 05:46:39 -05:00
last_id : usize ,
response : Option < Response > ,
2020-12-21 08:44:26 -05:00
state_snapshot : StateSnapshot ,
2021-02-28 19:53:20 -05:00
snapshots : HashMap < ( ModuleSpecifier , Cow < ' a , str > ) , String > ,
2020-12-07 05:46:39 -05:00
}
impl < ' a > State < ' a > {
2020-12-21 08:44:26 -05:00
fn new ( state_snapshot : StateSnapshot ) -> Self {
2020-12-07 05:46:39 -05:00
Self {
2020-12-15 14:34:39 -05:00
asset : None ,
2020-12-07 05:46:39 -05:00
last_id : 1 ,
response : None ,
2020-12-21 08:44:26 -05:00
state_snapshot ,
2020-12-07 05:46:39 -05:00
snapshots : Default ::default ( ) ,
}
}
}
/// If a snapshot is missing from the state cache, add it.
fn cache_snapshot (
state : & mut State ,
2021-02-28 19:53:20 -05:00
specifier : & ModuleSpecifier ,
2020-12-07 05:46:39 -05:00
version : String ,
) -> Result < ( ) , AnyError > {
if ! state
. snapshots
2021-02-28 19:53:20 -05:00
. contains_key ( & ( specifier . clone ( ) , version . clone ( ) . into ( ) ) )
2020-12-07 05:46:39 -05:00
{
2021-02-28 19:53:20 -05:00
let content = state
. state_snapshot
. documents
. content ( specifier ) ?
. ok_or_else ( | | {
anyhow! ( " Specifier unexpectedly doesn't have content: {} " , specifier )
} ) ? ;
2020-12-07 05:46:39 -05:00
state
. snapshots
2021-02-28 19:53:20 -05:00
. insert ( ( specifier . clone ( ) , version . into ( ) ) , content ) ;
2020-12-07 05:46:39 -05:00
}
Ok ( ( ) )
}
2021-02-24 22:15:55 -05:00
fn op < F , V , R > ( op_fn : F ) -> Box < OpFn >
2020-12-07 05:46:39 -05:00
where
2021-02-24 22:15:55 -05:00
F : Fn ( & mut State , V ) -> Result < R , AnyError > + 'static ,
V : de ::DeserializeOwned ,
R : Serialize ,
2020-12-07 05:46:39 -05:00
{
json_op_sync ( move | s , args , _bufs | {
let state = s . borrow_mut ::< State > ( ) ;
op_fn ( state , args )
} )
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct SourceSnapshotArgs {
specifier : String ,
version : String ,
}
/// The language service is dropping a reference to a source file snapshot, and
/// we can drop our version of that document.
2021-02-24 22:15:55 -05:00
#[ allow(clippy::unnecessary_wraps) ]
fn dispose (
state : & mut State ,
args : SourceSnapshotArgs ,
) -> Result < bool , AnyError > {
2021-02-11 23:17:48 -05:00
let mark = state . state_snapshot . performance . mark ( " op_dispose " ) ;
2021-02-28 19:53:20 -05:00
let specifier = resolve_url ( & args . specifier ) ? ;
state . snapshots . remove ( & ( specifier , args . version . into ( ) ) ) ;
2021-02-11 23:17:48 -05:00
state . state_snapshot . performance . measure ( mark ) ;
2021-02-24 22:15:55 -05:00
Ok ( true )
2020-12-07 05:46:39 -05:00
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct GetChangeRangeArgs {
specifier : String ,
old_length : u32 ,
old_version : String ,
version : String ,
}
/// The language service wants to compare an old snapshot with a new snapshot to
/// determine what source hash changed.
2021-02-24 22:15:55 -05:00
fn get_change_range (
state : & mut State ,
args : GetChangeRangeArgs ,
) -> Result < Value , AnyError > {
2021-02-11 23:17:48 -05:00
let mark = state . state_snapshot . performance . mark ( " op_get_change_range " ) ;
2021-02-28 19:53:20 -05:00
let specifier = resolve_url ( & args . specifier ) ? ;
if state . state_snapshot . documents . contains_key ( & specifier ) {
cache_snapshot ( state , & specifier , args . version . clone ( ) ) ? ;
}
2020-12-07 05:46:39 -05:00
if let Some ( current ) = state
. snapshots
2021-02-28 19:53:20 -05:00
. get ( & ( specifier . clone ( ) , args . version . clone ( ) . into ( ) ) )
2020-12-07 05:46:39 -05:00
{
2021-02-28 19:53:20 -05:00
if let Some ( prev ) = state
. snapshots
. get ( & ( specifier , args . old_version . clone ( ) . into ( ) ) )
{
2021-02-11 23:17:48 -05:00
state . state_snapshot . performance . measure ( mark ) ;
2020-12-07 05:46:39 -05:00
Ok ( text ::get_range_change ( prev , current ) )
} else {
2021-02-11 23:17:48 -05:00
let new_length = current . encode_utf16 ( ) . count ( ) ;
state . state_snapshot . performance . measure ( mark ) ;
2020-12-07 05:46:39 -05:00
// when a local file is opened up in the editor, the compiler might
// already have a snapshot of it in memory, and will request it, but we
// now are working off in memory versions of the document, and so need
// to tell tsc to reset the whole document
Ok ( json! ( {
" span " : {
" start " : 0 ,
2021-02-24 22:15:55 -05:00
" length " : args . old_length ,
2020-12-07 05:46:39 -05:00
} ,
2021-02-11 23:17:48 -05:00
" newLength " : new_length ,
2020-12-07 05:46:39 -05:00
} ) )
}
} else {
2021-02-11 23:17:48 -05:00
state . state_snapshot . performance . measure ( mark ) ;
2020-12-07 05:46:39 -05:00
Err ( custom_error (
" MissingSnapshot " ,
format! (
2021-02-24 22:15:55 -05:00
" The current snapshot version is missing. \n Args: \" {:?} \" " ,
2020-12-07 05:46:39 -05:00
args
) ,
) )
}
}
2021-02-24 22:15:55 -05:00
fn get_length (
state : & mut State ,
args : SourceSnapshotArgs ,
) -> Result < usize , AnyError > {
2021-02-11 23:17:48 -05:00
let mark = state . state_snapshot . performance . mark ( " op_get_length " ) ;
2021-02-24 22:15:55 -05:00
let specifier = resolve_url ( & args . specifier ) ? ;
2021-02-12 06:49:42 -05:00
if let Some ( Some ( asset ) ) = state . state_snapshot . assets . get ( & specifier ) {
2021-02-24 22:15:55 -05:00
Ok ( asset . length )
2021-02-12 06:49:42 -05:00
} else if state . state_snapshot . documents . contains_key ( & specifier ) {
2021-02-28 19:53:20 -05:00
cache_snapshot ( state , & specifier , args . version . clone ( ) ) ? ;
2020-12-07 05:46:39 -05:00
let content = state
. snapshots
2021-02-28 19:53:20 -05:00
. get ( & ( specifier , args . version . into ( ) ) )
2020-12-07 05:46:39 -05:00
. unwrap ( ) ;
2021-02-11 23:17:48 -05:00
state . state_snapshot . performance . measure ( mark ) ;
2021-02-24 22:15:55 -05:00
Ok ( content . encode_utf16 ( ) . count ( ) )
2020-12-07 05:46:39 -05:00
} else {
2021-02-06 07:39:01 -05:00
let sources = & mut state . state_snapshot . sources ;
2021-02-11 23:17:48 -05:00
state . state_snapshot . performance . measure ( mark ) ;
2021-02-24 22:15:55 -05:00
Ok ( sources . get_length_utf16 ( & specifier ) . unwrap ( ) )
2020-12-07 05:46:39 -05:00
}
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct GetTextArgs {
specifier : String ,
version : String ,
start : usize ,
end : usize ,
}
2021-02-24 22:15:55 -05:00
fn get_text ( state : & mut State , args : GetTextArgs ) -> Result < String , AnyError > {
2021-02-11 23:17:48 -05:00
let mark = state . state_snapshot . performance . mark ( " op_get_text " ) ;
2021-02-24 22:15:55 -05:00
let specifier = resolve_url ( & args . specifier ) ? ;
2021-02-12 06:49:42 -05:00
let content =
if let Some ( Some ( content ) ) = state . state_snapshot . assets . get ( & specifier ) {
content . text . clone ( )
} else if state . state_snapshot . documents . contains_key ( & specifier ) {
2021-02-28 19:53:20 -05:00
cache_snapshot ( state , & specifier , args . version . clone ( ) ) ? ;
2021-02-12 06:49:42 -05:00
state
. snapshots
2021-02-28 19:53:20 -05:00
. get ( & ( specifier , args . version . into ( ) ) )
2021-02-12 06:49:42 -05:00
. unwrap ( )
. clone ( )
} else {
2021-02-15 04:32:06 -05:00
state . state_snapshot . sources . get_source ( & specifier ) . unwrap ( )
2021-02-12 06:49:42 -05:00
} ;
2021-02-11 23:17:48 -05:00
state . state_snapshot . performance . measure ( mark ) ;
2021-02-24 22:15:55 -05:00
Ok ( text ::slice ( & content , args . start .. args . end ) . to_string ( ) )
2020-12-07 05:46:39 -05:00
}
2021-02-24 22:15:55 -05:00
fn resolve (
state : & mut State ,
args : ResolveArgs ,
) -> Result < Vec < Option < ( String , String ) > > , AnyError > {
2021-02-11 23:17:48 -05:00
let mark = state . state_snapshot . performance . mark ( " op_resolve " ) ;
2021-02-24 22:15:55 -05:00
let mut resolved = Vec ::new ( ) ;
let referrer = resolve_url ( & args . base ) ? ;
2021-02-06 07:39:01 -05:00
let sources = & mut state . state_snapshot . sources ;
2020-12-07 05:46:39 -05:00
2021-02-12 06:49:42 -05:00
if state . state_snapshot . documents . contains_key ( & referrer ) {
2021-01-25 18:47:12 -05:00
if let Some ( dependencies ) =
state . state_snapshot . documents . dependencies ( & referrer )
{
2021-02-24 22:15:55 -05:00
for specifier in & args . specifiers {
2020-12-07 05:46:39 -05:00
if specifier . starts_with ( " asset:/// " ) {
resolved . push ( Some ( (
specifier . clone ( ) ,
2021-02-17 23:37:05 -05:00
MediaType ::from ( specifier ) . as_ts_extension ( ) . into ( ) ,
2020-12-07 05:46:39 -05:00
) ) )
} else if let Some ( dependency ) = dependencies . get ( specifier ) {
let resolved_import =
if let Some ( resolved_import ) = & dependency . maybe_type {
resolved_import . clone ( )
} else if let Some ( resolved_import ) = & dependency . maybe_code {
resolved_import . clone ( )
} else {
2021-02-09 17:46:12 -05:00
ResolvedDependency ::Err ( ResolvedDependencyErr ::Missing )
2020-12-07 05:46:39 -05:00
} ;
2020-12-24 05:53:03 -05:00
if let ResolvedDependency ::Resolved ( resolved_specifier ) =
resolved_import
2020-12-07 05:46:39 -05:00
{
2021-02-12 06:49:42 -05:00
if state
. state_snapshot
. documents
. contains_key ( & resolved_specifier )
{
2021-02-11 23:17:48 -05:00
let media_type = MediaType ::from ( & resolved_specifier ) ;
resolved . push ( Some ( (
resolved_specifier . to_string ( ) ,
2021-02-17 23:37:05 -05:00
media_type . as_ts_extension ( ) . into ( ) ,
2021-02-11 23:17:48 -05:00
) ) ) ;
2021-02-12 06:49:42 -05:00
} else if sources . contains_key ( & resolved_specifier ) {
2020-12-09 14:50:47 -05:00
let media_type = if let Some ( media_type ) =
sources . get_media_type ( & resolved_specifier )
{
media_type
} else {
MediaType ::from ( & resolved_specifier )
} ;
resolved . push ( Some ( (
resolved_specifier . to_string ( ) ,
2021-02-17 23:37:05 -05:00
media_type . as_ts_extension ( ) . into ( ) ,
2020-12-09 14:50:47 -05:00
) ) ) ;
2020-12-07 05:46:39 -05:00
} else {
2020-12-09 14:50:47 -05:00
resolved . push ( None ) ;
}
2020-12-07 05:46:39 -05:00
} else {
resolved . push ( None ) ;
}
}
}
}
2021-02-12 06:49:42 -05:00
} else if sources . contains_key ( & referrer ) {
2021-02-24 22:15:55 -05:00
for specifier in & args . specifiers {
2020-12-07 05:46:39 -05:00
if let Some ( ( resolved_specifier , media_type ) ) =
sources . resolve_import ( specifier , & referrer )
{
resolved . push ( Some ( (
resolved_specifier . to_string ( ) ,
2021-02-17 23:37:05 -05:00
media_type . as_ts_extension ( ) . into ( ) ,
2020-12-07 05:46:39 -05:00
) ) ) ;
} else {
resolved . push ( None ) ;
}
}
} else {
2021-02-11 23:17:48 -05:00
state . state_snapshot . performance . measure ( mark ) ;
2020-12-07 05:46:39 -05:00
return Err ( custom_error (
" NotFound " ,
2021-01-22 05:03:16 -05:00
format! (
" the referring ({}) specifier is unexpectedly missing " ,
referrer
) ,
2020-12-07 05:46:39 -05:00
) ) ;
}
2021-02-11 23:17:48 -05:00
state . state_snapshot . performance . measure ( mark ) ;
2021-02-24 22:15:55 -05:00
Ok ( resolved )
2020-12-07 05:46:39 -05:00
}
2021-02-24 22:15:55 -05:00
#[ allow(clippy::unnecessary_wraps) ]
fn respond ( state : & mut State , args : Response ) -> Result < bool , AnyError > {
state . response = Some ( args ) ;
Ok ( true )
2020-12-07 05:46:39 -05:00
}
2021-02-12 05:08:36 -05:00
#[ allow(clippy::unnecessary_wraps) ]
2021-02-24 22:15:55 -05:00
fn script_names (
state : & mut State ,
_args : Value ,
) -> Result < Vec < ModuleSpecifier > , AnyError > {
Ok (
state
. state_snapshot
. documents
. open_specifiers ( )
. into_iter ( )
. cloned ( )
. collect ( ) ,
)
2020-12-07 05:46:39 -05:00
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct ScriptVersionArgs {
specifier : String ,
}
2021-02-24 22:15:55 -05:00
fn script_version (
state : & mut State ,
args : ScriptVersionArgs ,
) -> Result < Option < String > , AnyError > {
2021-02-11 23:17:48 -05:00
let mark = state . state_snapshot . performance . mark ( " op_script_version " ) ;
2021-02-24 22:15:55 -05:00
let specifier = resolve_url ( & args . specifier ) ? ;
2021-02-17 13:47:18 -05:00
if specifier . scheme ( ) = = " asset " {
2021-02-12 06:49:42 -05:00
return if state . state_snapshot . assets . contains_key ( & specifier ) {
2021-02-24 22:15:55 -05:00
Ok ( Some ( " 1 " . to_string ( ) ) )
2021-02-12 06:49:42 -05:00
} else {
2021-02-24 22:15:55 -05:00
Ok ( None )
2021-02-12 06:49:42 -05:00
} ;
} else if let Some ( version ) =
state . state_snapshot . documents . version ( & specifier )
{
2021-02-24 22:15:55 -05:00
return Ok ( Some ( version . to_string ( ) ) ) ;
2020-12-07 05:46:39 -05:00
} else {
2021-02-06 07:39:01 -05:00
let sources = & mut state . state_snapshot . sources ;
2020-12-07 05:46:39 -05:00
if let Some ( version ) = sources . get_script_version ( & specifier ) {
2021-02-24 22:15:55 -05:00
return Ok ( Some ( version ) ) ;
2020-12-07 05:46:39 -05:00
}
}
2021-02-11 23:17:48 -05:00
state . state_snapshot . performance . measure ( mark ) ;
2021-02-24 22:15:55 -05:00
Ok ( None )
2020-12-07 05:46:39 -05:00
}
2020-12-15 14:34:39 -05:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct SetAssetArgs {
text : Option < String > ,
}
2021-02-24 22:15:55 -05:00
#[ allow(clippy::unnecessary_wraps) ]
fn set_asset ( state : & mut State , args : SetAssetArgs ) -> Result < bool , AnyError > {
state . asset = args . text ;
Ok ( true )
2020-12-15 14:34:39 -05:00
}
2020-12-07 05:46:39 -05:00
/// 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.
pub fn start ( debug : bool ) -> Result < JsRuntime , AnyError > {
let mut runtime = JsRuntime ::new ( RuntimeOptions {
2021-01-05 20:38:23 -05:00
startup_snapshot : Some ( tsc ::compiler_snapshot ( ) ) ,
2020-12-07 05:46:39 -05:00
.. Default ::default ( )
} ) ;
{
let op_state = runtime . op_state ( ) ;
let mut op_state = op_state . borrow_mut ( ) ;
2020-12-21 08:44:26 -05:00
op_state . put ( State ::new ( StateSnapshot ::default ( ) ) ) ;
2020-12-07 05:46:39 -05:00
}
runtime . register_op ( " op_dispose " , op ( dispose ) ) ;
runtime . register_op ( " op_get_change_range " , op ( get_change_range ) ) ;
runtime . register_op ( " op_get_length " , op ( get_length ) ) ;
runtime . register_op ( " op_get_text " , op ( get_text ) ) ;
runtime . register_op ( " op_resolve " , op ( resolve ) ) ;
runtime . register_op ( " op_respond " , op ( respond ) ) ;
runtime . register_op ( " op_script_names " , op ( script_names ) ) ;
runtime . register_op ( " op_script_version " , op ( script_version ) ) ;
2020-12-15 14:34:39 -05:00
runtime . register_op ( " op_set_asset " , op ( set_asset ) ) ;
2020-12-07 05:46:39 -05:00
let init_config = json! ( { " debug " : debug } ) ;
let init_src = format! ( " globalThis.serverInit( {} ); " , init_config ) ;
runtime . execute ( " [native code] " , & init_src ) ? ;
Ok ( runtime )
}
2020-12-08 05:36:13 -05:00
#[ derive(Debug, Serialize) ]
#[ serde(rename_all = " kebab-case " ) ]
#[ allow(dead_code) ]
pub enum QuotePreference {
Auto ,
Double ,
Single ,
}
#[ derive(Debug, Serialize) ]
#[ serde(rename_all = " kebab-case " ) ]
#[ allow(dead_code) ]
pub enum ImportModuleSpecifierPreference {
Auto ,
Relative ,
NonRelative ,
}
#[ derive(Debug, Serialize) ]
#[ serde(rename_all = " kebab-case " ) ]
#[ allow(dead_code) ]
pub enum ImportModuleSpecifierEnding {
Auto ,
Minimal ,
Index ,
Js ,
}
#[ derive(Debug, Serialize) ]
#[ serde(rename_all = " kebab-case " ) ]
#[ allow(dead_code) ]
pub enum IncludePackageJsonAutoImports {
Auto ,
On ,
Off ,
}
#[ 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 " ) ]
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 " ) ]
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 > ,
}
2021-02-15 21:34:09 -05: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 ,
}
impl From < lsp ::SignatureHelpTriggerKind > for SignatureHelpTriggerKind {
fn from ( kind : lsp ::SignatureHelpTriggerKind ) -> Self {
match kind {
lsp ::SignatureHelpTriggerKind ::Invoked = > Self ::Invoked ,
lsp ::SignatureHelpTriggerKind ::TriggerCharacter = > Self ::CharacterTyped ,
lsp ::SignatureHelpTriggerKind ::ContentChange = > Self ::Retrigger ,
}
}
}
#[ 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 > ,
}
2020-12-07 05:46:39 -05:00
/// Methods that are supported by the Language Service in the compiler isolate.
2021-01-22 05:03:16 -05:00
#[ derive(Debug) ]
2020-12-07 05:46:39 -05:00
pub enum RequestMethod {
/// Configure the compilation settings for the server.
Configure ( TsConfig ) ,
2021-01-31 22:30:41 -05:00
/// Get rename locations at a given position.
FindRenameLocations ( ( ModuleSpecifier , u32 , bool , bool , bool ) ) ,
2020-12-15 14:34:39 -05:00
/// Retrieve the text of an assets that exists in memory in the isolate.
GetAsset ( ModuleSpecifier ) ,
2021-02-04 13:53:02 -05:00
/// Retrieve code fixes for a range of a file with the provided error codes.
GetCodeFixes ( ( ModuleSpecifier , u32 , u32 , Vec < String > ) ) ,
2021-01-31 22:30:41 -05:00
/// Get completion information at a given position (IntelliSense).
GetCompletions ( ( ModuleSpecifier , u32 , UserPreferences ) ) ,
2021-02-04 13:53:02 -05:00
/// Retrieve the combined code fixes for a fix id for a module.
GetCombinedCodeFix ( ( ModuleSpecifier , Value ) ) ,
2021-01-31 22:30:41 -05:00
/// Get declaration information for a specific position.
GetDefinition ( ( ModuleSpecifier , u32 ) ) ,
2020-12-29 20:46:58 -05:00
/// Return diagnostics for given file.
2021-01-22 05:03:16 -05:00
GetDiagnostics ( Vec < ModuleSpecifier > ) ,
2020-12-07 05:46:39 -05:00
/// Return document highlights at position.
GetDocumentHighlights ( ( ModuleSpecifier , u32 , Vec < ModuleSpecifier > ) ) ,
2021-01-12 16:53:27 -05:00
/// Get implementation information for a specific position.
GetImplementation ( ( ModuleSpecifier , u32 ) ) ,
2021-01-31 22:30:41 -05:00
/// Get a "navigation tree" for a specifier.
GetNavigationTree ( ModuleSpecifier ) ,
/// Return quick info at position (hover information).
GetQuickInfo ( ( ModuleSpecifier , u32 ) ) ,
/// Get document references for a specific position.
GetReferences ( ( ModuleSpecifier , u32 ) ) ,
2021-02-15 21:34:09 -05:00
/// Get signature help items for a specific position.
GetSignatureHelpItems ( ( ModuleSpecifier , u32 , SignatureHelpItemsOptions ) ) ,
2021-02-04 13:53:02 -05:00
/// Get the diagnostic codes that support some form of code fix.
GetSupportedCodeFixes ,
2020-12-07 05:46:39 -05:00
}
impl RequestMethod {
pub fn to_value ( & self , id : usize ) -> Value {
match self {
RequestMethod ::Configure ( config ) = > json! ( {
" id " : id ,
" method " : " configure " ,
" compilerOptions " : config ,
} ) ,
2021-01-31 22:30:41 -05:00
RequestMethod ::FindRenameLocations ( (
specifier ,
position ,
find_in_strings ,
find_in_comments ,
provide_prefix_and_suffix_text_for_rename ,
) ) = > {
json! ( {
" id " : id ,
" method " : " findRenameLocations " ,
" specifier " : specifier ,
" position " : position ,
" findInStrings " : find_in_strings ,
" findInComments " : find_in_comments ,
" providePrefixAndSuffixTextForRename " : provide_prefix_and_suffix_text_for_rename
} )
}
2020-12-15 14:34:39 -05:00
RequestMethod ::GetAsset ( specifier ) = > json! ( {
" id " : id ,
" method " : " getAsset " ,
" specifier " : specifier ,
} ) ,
2021-02-04 13:53:02 -05:00
RequestMethod ::GetCodeFixes ( (
specifier ,
start_pos ,
end_pos ,
error_codes ,
) ) = > json! ( {
" id " : id ,
" method " : " getCodeFixes " ,
" specifier " : specifier ,
" startPosition " : start_pos ,
" endPosition " : end_pos ,
" errorCodes " : error_codes ,
} ) ,
RequestMethod ::GetCombinedCodeFix ( ( specifier , fix_id ) ) = > json! ( {
" id " : id ,
" method " : " getCombinedCodeFix " ,
" specifier " : specifier ,
" fixId " : fix_id ,
} ) ,
2021-01-31 22:30:41 -05:00
RequestMethod ::GetCompletions ( ( specifier , position , preferences ) ) = > {
json! ( {
" id " : id ,
" method " : " getCompletions " ,
" specifier " : specifier ,
" position " : position ,
" preferences " : preferences ,
} )
}
RequestMethod ::GetDefinition ( ( specifier , position ) ) = > json! ( {
" id " : id ,
" method " : " getDefinition " ,
" specifier " : specifier ,
" position " : position ,
} ) ,
2021-01-22 05:03:16 -05:00
RequestMethod ::GetDiagnostics ( specifiers ) = > json! ( {
2020-12-07 05:46:39 -05:00
" id " : id ,
2020-12-29 20:46:58 -05:00
" method " : " getDiagnostics " ,
2021-01-22 05:03:16 -05:00
" specifiers " : specifiers ,
2020-12-07 05:46:39 -05:00
} ) ,
RequestMethod ::GetDocumentHighlights ( (
specifier ,
position ,
files_to_search ,
) ) = > json! ( {
" id " : id ,
" method " : " getDocumentHighlights " ,
" specifier " : specifier ,
" position " : position ,
" filesToSearch " : files_to_search ,
} ) ,
2021-01-31 22:30:41 -05:00
RequestMethod ::GetImplementation ( ( specifier , position ) ) = > json! ( {
2020-12-07 05:46:39 -05:00
" id " : id ,
2021-01-31 22:30:41 -05:00
" method " : " getImplementation " ,
2020-12-07 05:46:39 -05:00
" specifier " : specifier ,
" position " : position ,
} ) ,
2021-01-31 22:30:41 -05:00
RequestMethod ::GetNavigationTree ( specifier ) = > json! ( {
2020-12-07 05:46:39 -05:00
" id " : id ,
2021-01-31 22:30:41 -05:00
" method " : " getNavigationTree " ,
" specifier " : specifier ,
} ) ,
RequestMethod ::GetQuickInfo ( ( specifier , position ) ) = > json! ( {
" id " : id ,
" method " : " getQuickInfo " ,
2020-12-07 05:46:39 -05:00
" specifier " : specifier ,
" position " : position ,
} ) ,
2021-01-31 22:30:41 -05:00
RequestMethod ::GetReferences ( ( specifier , position ) ) = > json! ( {
" id " : id ,
" method " : " getReferences " ,
" specifier " : specifier ,
" position " : position ,
2021-01-12 16:53:27 -05:00
} ) ,
2021-02-15 21:34:09 -05:00
RequestMethod ::GetSignatureHelpItems ( ( specifier , position , options ) ) = > {
json! ( {
" id " : id ,
" method " : " getSignatureHelpItems " ,
" specifier " : specifier ,
" position " : position ,
" options " : options ,
} )
}
2021-02-04 13:53:02 -05:00
RequestMethod ::GetSupportedCodeFixes = > json! ( {
" id " : id ,
" method " : " getSupportedCodeFixes " ,
} ) ,
2020-12-07 05:46:39 -05:00
}
}
}
/// Send a request into a runtime and return the JSON value of the response.
pub fn request (
runtime : & mut JsRuntime ,
2020-12-21 08:44:26 -05:00
state_snapshot : StateSnapshot ,
2020-12-07 05:46:39 -05:00
method : RequestMethod ,
) -> Result < Value , AnyError > {
let id = {
let op_state = runtime . op_state ( ) ;
let mut op_state = op_state . borrow_mut ( ) ;
let state = op_state . borrow_mut ::< State > ( ) ;
2020-12-21 08:44:26 -05:00
state . state_snapshot = state_snapshot ;
2020-12-07 05:46:39 -05:00
state . last_id + = 1 ;
state . last_id
} ;
let request_params = method . to_value ( id ) ;
let request_src = format! ( " globalThis.serverRequest( {} ); " , request_params ) ;
runtime . execute ( " [native_code] " , & request_src ) ? ;
let op_state = runtime . op_state ( ) ;
let mut op_state = op_state . borrow_mut ( ) ;
let state = op_state . borrow_mut ::< State > ( ) ;
if let Some ( response ) = state . response . clone ( ) {
state . response = None ;
Ok ( response . data )
} else {
Err ( custom_error (
" RequestError " ,
" The response was not received for the request. " ,
) )
}
}
#[ cfg(test) ]
mod tests {
use super ::* ;
2021-01-22 05:03:16 -05:00
use crate ::lsp ::documents ::DocumentCache ;
2020-12-07 05:46:39 -05:00
2020-12-21 08:44:26 -05:00
fn mock_state_snapshot ( sources : Vec < ( & str , & str , i32 ) > ) -> StateSnapshot {
2021-01-22 05:03:16 -05:00
let mut documents = DocumentCache ::default ( ) ;
2020-12-07 05:46:39 -05:00
for ( specifier , content , version ) in sources {
2021-02-17 13:47:18 -05:00
let specifier =
resolve_url ( specifier ) . expect ( " failed to create specifier " ) ;
2021-02-09 17:46:12 -05:00
documents . open ( specifier , version , content ) ;
2020-12-07 05:46:39 -05:00
}
2020-12-21 08:44:26 -05:00
StateSnapshot {
2021-01-25 18:47:12 -05:00
documents ,
2021-02-11 23:17:48 -05:00
.. Default ::default ( )
2020-12-07 05:46:39 -05:00
}
}
fn setup (
debug : bool ,
config : Value ,
sources : Vec < ( & str , & str , i32 ) > ,
2020-12-21 08:44:26 -05:00
) -> ( JsRuntime , StateSnapshot ) {
let state_snapshot = mock_state_snapshot ( sources . clone ( ) ) ;
2020-12-07 05:46:39 -05:00
let mut runtime = start ( debug ) . expect ( " could not start server " ) ;
let ts_config = TsConfig ::new ( config ) ;
assert_eq! (
request (
& mut runtime ,
2020-12-21 08:44:26 -05:00
state_snapshot . clone ( ) ,
2020-12-07 05:46:39 -05:00
RequestMethod ::Configure ( ts_config )
)
. expect ( " failed request " ) ,
json! ( true )
) ;
2020-12-21 08:44:26 -05:00
( runtime , state_snapshot )
2020-12-07 05:46:39 -05: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" ) ;
}
#[ test ]
fn test_project_configure ( ) {
setup (
false ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" noEmit " : true ,
} ) ,
vec! [ ] ,
) ;
}
#[ test ]
fn test_project_reconfigure ( ) {
2020-12-21 08:44:26 -05:00
let ( mut runtime , state_snapshot ) = setup (
2020-12-07 05:46:39 -05:00
false ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" noEmit " : true ,
} ) ,
vec! [ ] ,
) ;
let ts_config = TsConfig ::new ( json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" noEmit " : true ,
" lib " : [ " deno.ns " , " deno.worker " ]
} ) ) ;
let result = request (
& mut runtime ,
2020-12-21 08:44:26 -05:00
state_snapshot ,
2020-12-07 05:46:39 -05:00
RequestMethod ::Configure ( ts_config ) ,
) ;
assert! ( result . is_ok ( ) ) ;
let response = result . unwrap ( ) ;
assert_eq! ( response , json! ( true ) ) ;
}
#[ test ]
2020-12-29 20:46:58 -05:00
fn test_get_diagnostics ( ) {
2020-12-21 08:44:26 -05:00
let ( mut runtime , state_snapshot ) = setup (
2020-12-07 05:46:39 -05:00
false ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" noEmit " : true ,
} ) ,
vec! [ ( " file:///a.ts " , r # "console.log("hello deno");"# , 1 ) ] ,
) ;
2021-02-17 13:47:18 -05:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2020-12-07 05:46:39 -05:00
let result = request (
& mut runtime ,
2020-12-21 08:44:26 -05:00
state_snapshot ,
2021-01-22 05:03:16 -05:00
RequestMethod ::GetDiagnostics ( vec! [ specifier ] ) ,
2020-12-07 05:46:39 -05:00
) ;
assert! ( result . is_ok ( ) ) ;
let response = result . unwrap ( ) ;
assert_eq! (
response ,
2021-01-22 05:03:16 -05:00
json! ( {
" file:///a.ts " : [
{
" start " : {
" line " : 0 ,
" character " : 0 ,
} ,
" end " : {
" line " : 0 ,
" character " : 7
} ,
" fileName " : " file:///a.ts " ,
" messageText " : " Cannot find name 'console'. Do you need to change your target library? Try changing the `lib` compiler option to include 'dom'. " ,
" sourceLine " : " console.log( \" hello deno \" ); " ,
" category " : 1 ,
" code " : 2584
}
]
} )
2020-12-07 05:46:39 -05:00
) ;
}
2021-02-12 06:49:42 -05:00
#[ test ]
fn test_get_diagnostics_lib ( ) {
let ( mut runtime , state_snapshot ) = setup (
false ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" jsx " : " react " ,
" lib " : [ " esnext " , " dom " , " deno.ns " ] ,
" noEmit " : true ,
} ) ,
vec! [ ( " file:///a.ts " , r # "console.log(document.location);"# , 1 ) ] ,
) ;
2021-02-17 13:47:18 -05:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2021-02-12 06:49:42 -05:00
let result = request (
& mut runtime ,
state_snapshot ,
RequestMethod ::GetDiagnostics ( vec! [ specifier ] ) ,
) ;
assert! ( result . is_ok ( ) ) ;
let response = result . unwrap ( ) ;
assert_eq! ( response , json! ( { " file:///a.ts " : [ ] } ) ) ;
}
2020-12-07 05:46:39 -05:00
#[ test ]
fn test_module_resolution ( ) {
2020-12-21 08:44:26 -05:00
let ( mut runtime , state_snapshot ) = setup (
2020-12-07 05:46:39 -05:00
false ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
vec! [ (
" file:///a.ts " ,
r #"
import { B } from " https://deno.land/x/b/mod.ts " ;
const b = new B ( ) ;
console . log ( b ) ;
" #,
1 ,
) ] ,
) ;
2021-02-17 13:47:18 -05:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2020-12-07 05:46:39 -05:00
let result = request (
& mut runtime ,
2020-12-21 08:44:26 -05:00
state_snapshot ,
2021-01-22 05:03:16 -05:00
RequestMethod ::GetDiagnostics ( vec! [ specifier ] ) ,
2020-12-07 05:46:39 -05:00
) ;
assert! ( result . is_ok ( ) ) ;
let response = result . unwrap ( ) ;
2021-01-22 05:03:16 -05:00
assert_eq! ( response , json! ( { " file:///a.ts " : [ ] } ) ) ;
2020-12-07 05:46:39 -05:00
}
#[ test ]
fn test_bad_module_specifiers ( ) {
2020-12-21 08:44:26 -05:00
let ( mut runtime , state_snapshot ) = setup (
2020-12-07 05:46:39 -05:00
false ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
vec! [ (
" file:///a.ts " ,
r #"
import { A } from " . " ;
" #,
1 ,
) ] ,
) ;
2021-02-17 13:47:18 -05:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2020-12-07 05:46:39 -05:00
let result = request (
& mut runtime ,
2020-12-21 08:44:26 -05:00
state_snapshot ,
2021-01-22 05:03:16 -05:00
RequestMethod ::GetDiagnostics ( vec! [ specifier ] ) ,
2020-12-07 05:46:39 -05:00
) ;
assert! ( result . is_ok ( ) ) ;
let response = result . unwrap ( ) ;
2020-12-29 20:46:58 -05:00
assert_eq! (
response ,
2021-01-22 05:03:16 -05: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 ,
" reportsUnnecessary " : true ,
} ]
} )
2020-12-29 20:46:58 -05:00
) ;
2020-12-07 05:46:39 -05:00
}
#[ test ]
fn test_remote_modules ( ) {
2020-12-21 08:44:26 -05:00
let ( mut runtime , state_snapshot ) = setup (
2020-12-07 05:46:39 -05:00
false ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
vec! [ (
" file:///a.ts " ,
r #"
import { B } from " https://deno.land/x/b/mod.ts " ;
const b = new B ( ) ;
console . log ( b ) ;
" #,
1 ,
) ] ,
) ;
2021-02-17 13:47:18 -05:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2020-12-07 05:46:39 -05:00
let result = request (
& mut runtime ,
2020-12-21 08:44:26 -05:00
state_snapshot ,
2021-01-22 05:03:16 -05:00
RequestMethod ::GetDiagnostics ( vec! [ specifier ] ) ,
2020-12-07 05:46:39 -05:00
) ;
assert! ( result . is_ok ( ) ) ;
let response = result . unwrap ( ) ;
2021-01-22 05:03:16 -05:00
assert_eq! ( response , json! ( { " file:///a.ts " : [ ] } ) ) ;
2020-12-07 05:46:39 -05:00
}
#[ test ]
fn test_partial_modules ( ) {
2020-12-21 08:44:26 -05:00
let ( mut runtime , state_snapshot ) = setup (
2020-12-07 05:46:39 -05:00
false ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
vec! [ (
" file:///a.ts " ,
r #"
import {
Application ,
Context ,
Router ,
Status ,
} from " https://deno.land/x/oak@v6.3.2/mod.ts " ;
2021-01-29 14:34:33 -05:00
2020-12-07 05:46:39 -05:00
import * as test from
" #,
1 ,
) ] ,
) ;
2021-02-17 13:47:18 -05:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2020-12-07 05:46:39 -05:00
let result = request (
& mut runtime ,
2020-12-21 08:44:26 -05:00
state_snapshot ,
2021-01-22 05:03:16 -05:00
RequestMethod ::GetDiagnostics ( vec! [ specifier ] ) ,
2020-12-07 05:46:39 -05:00
) ;
2020-12-15 14:34:39 -05:00
assert! ( result . is_ok ( ) ) ;
let response = result . unwrap ( ) ;
assert_eq! (
response ,
2021-01-22 05:03:16 -05: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 ,
" reportsUnnecessary " : true
} , {
" 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 14:34:39 -05:00
) ;
}
2020-12-29 20:46:58 -05:00
#[ test ]
fn test_no_debug_failure ( ) {
let ( mut runtime , state_snapshot ) = setup (
false ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
vec! [ ( " file:///a.ts " , r # "const url = new URL("b.js", import."# , 1 ) ] ,
) ;
2021-02-17 13:47:18 -05:00
let specifier = resolve_url ( " file:///a.ts " ) . expect ( " could not resolve url " ) ;
2020-12-29 20:46:58 -05:00
let result = request (
& mut runtime ,
state_snapshot ,
2021-01-22 05:03:16 -05:00
RequestMethod ::GetDiagnostics ( vec! [ specifier ] ) ,
2020-12-29 20:46:58 -05:00
) ;
assert! ( result . is_ok ( ) ) ;
let response = result . unwrap ( ) ;
2021-01-22 05:03:16 -05:00
assert_eq! ( response , json! ( { } ) ) ;
2020-12-29 20:46:58 -05:00
}
2020-12-15 14:34:39 -05:00
#[ test ]
fn test_request_asset ( ) {
2020-12-21 08:44:26 -05:00
let ( mut runtime , state_snapshot ) = setup (
2020-12-15 14:34:39 -05:00
false ,
json! ( {
" target " : " esnext " ,
" module " : " esnext " ,
" lib " : [ " deno.ns " , " deno.window " ] ,
" noEmit " : true ,
} ) ,
vec! [ ] ,
) ;
2021-02-17 13:47:18 -05:00
let specifier =
resolve_url ( " asset:///lib.esnext.d.ts " ) . expect ( " could not resolve url " ) ;
2020-12-21 08:44:26 -05:00
let result = request (
& mut runtime ,
state_snapshot ,
RequestMethod ::GetAsset ( specifier ) ,
) ;
2020-12-15 14:34:39 -05:00
assert! ( result . is_ok ( ) ) ;
2020-12-21 08:44:26 -05:00
let response : Option < String > =
serde_json ::from_value ( result . unwrap ( ) ) . unwrap ( ) ;
2020-12-15 14:34:39 -05:00
assert! ( response . is_some ( ) ) ;
2020-12-07 05:46:39 -05:00
}
}