2023-01-02 16:00:42 -05:00
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
2020-12-07 05:46:39 -05:00
2021-05-11 10:43:00 -04:00
use super ::analysis ;
2022-02-01 21:04:26 -05:00
use super ::cache ;
2021-12-15 13:23:43 -05:00
use super ::client ::Client ;
2022-01-19 17:10:14 -05:00
use super ::config ::ConfigSnapshot ;
2021-10-28 19:56:01 -04:00
use super ::documents ;
2022-01-29 17:50:15 -05:00
use super ::documents ::Document ;
2023-03-29 16:25:48 -04:00
use super ::documents ::DocumentsFilter ;
2021-03-09 21:41:35 -05:00
use super ::language_server ;
2022-02-02 18:02:59 -05:00
use super ::language_server ::StateSnapshot ;
2022-01-17 17:09:43 -05:00
use super ::performance ::Performance ;
2020-12-07 05:46:39 -05:00
use super ::tsc ;
2022-01-17 17:09:43 -05:00
use super ::tsc ::TsServer ;
2023-08-02 16:57:25 -04:00
use super ::urls ::LspClientUrl ;
use super ::urls ::LspUrlMap ;
2020-12-07 05:46:39 -05:00
2023-01-07 15:22:09 -05:00
use crate ::args ::LintOptions ;
2023-01-27 17:36:23 -05:00
use crate ::graph_util ;
2023-01-24 15:14:49 -05:00
use crate ::graph_util ::enhanced_resolution_error_message ;
2023-05-26 02:10:18 -04:00
use crate ::lsp ::lsp_custom ::DiagnosticBatchNotificationParams ;
2023-01-07 15:22:09 -05:00
use crate ::tools ::lint ::get_configured_rules ;
2020-12-07 05:46:39 -05:00
2021-12-15 22:53:17 -05:00
use deno_ast ::MediaType ;
2021-11-16 09:02:28 -05:00
use deno_core ::anyhow ::anyhow ;
2020-12-07 05:46:39 -05:00
use deno_core ::error ::AnyError ;
2023-09-24 18:33:52 -04:00
use deno_core ::parking_lot ::RwLock ;
2021-05-11 10:43:00 -04:00
use deno_core ::resolve_url ;
2022-02-04 02:14:57 -05:00
use deno_core ::serde ::Deserialize ;
use deno_core ::serde_json ;
2021-05-19 08:28:23 -04:00
use deno_core ::serde_json ::json ;
2023-08-23 19:03:05 -04:00
use deno_core ::unsync ::spawn ;
use deno_core ::unsync ::spawn_blocking ;
use deno_core ::unsync ::JoinHandle ;
2021-01-22 05:03:16 -05:00
use deno_core ::ModuleSpecifier ;
2023-02-09 22:00:23 -05:00
use deno_graph ::Resolution ;
2023-01-24 15:14:49 -05:00
use deno_graph ::ResolutionError ;
use deno_graph ::SpecifierError ;
2023-01-07 15:22:09 -05:00
use deno_lint ::rules ::LintRule ;
2023-04-21 21:02:46 -04:00
use deno_runtime ::deno_node ;
2021-10-21 07:05:43 -04:00
use deno_runtime ::tokio_util ::create_basic_runtime ;
2023-04-06 18:46:44 -04:00
use deno_semver ::npm ::NpmPackageReqReference ;
2023-08-21 05:53:52 -04:00
use deno_semver ::package ::PackageReq ;
2021-03-26 12:34:25 -04:00
use log ::error ;
2020-12-07 05:46:39 -05:00
use std ::collections ::HashMap ;
2023-07-11 17:10:43 -04:00
use std ::collections ::HashSet ;
2023-05-26 02:10:18 -04:00
use std ::sync ::atomic ::AtomicUsize ;
2021-03-09 21:41:35 -05:00
use std ::sync ::Arc ;
use std ::thread ;
use tokio ::sync ::mpsc ;
2021-05-11 10:43:00 -04:00
use tokio ::sync ::Mutex ;
2021-03-18 16:26:41 -04:00
use tokio ::time ::Duration ;
2022-01-24 15:30:01 -05:00
use tokio_util ::sync ::CancellationToken ;
2022-04-03 00:17:30 -04:00
use tower_lsp ::lsp_types as lsp ;
2020-12-07 05:46:39 -05:00
2023-05-26 02:10:18 -04:00
#[ derive(Debug) ]
pub struct DiagnosticServerUpdateMessage {
pub snapshot : Arc < StateSnapshot > ,
pub config : Arc < ConfigSnapshot > ,
pub lint_options : LintOptions ,
2023-08-02 16:57:25 -04:00
pub url_map : LspUrlMap ,
2023-05-26 02:10:18 -04:00
}
2023-07-11 16:36:39 -04:00
struct DiagnosticRecord {
pub specifier : ModuleSpecifier ,
pub versioned : VersionedDiagnostics ,
}
#[ derive(Clone, Default, Debug) ]
struct VersionedDiagnostics {
pub version : Option < i32 > ,
pub diagnostics : Vec < lsp ::Diagnostic > ,
}
type DiagnosticVec = Vec < DiagnosticRecord > ;
2022-01-24 18:04:24 -05:00
2023-07-11 17:10:43 -04:00
#[ derive(Debug, Hash, PartialEq, Eq, Copy, Clone) ]
pub enum DiagnosticSource {
Deno ,
Lint ,
Ts ,
}
impl DiagnosticSource {
pub fn as_lsp_source ( & self ) -> & 'static str {
match self {
Self ::Deno = > " deno " ,
Self ::Lint = > " deno-lint " ,
Self ::Ts = > " deno-ts " ,
}
}
}
type DiagnosticsBySource = HashMap < DiagnosticSource , VersionedDiagnostics > ;
#[ derive(Debug) ]
2022-01-24 18:04:24 -05:00
struct DiagnosticsPublisher {
client : Client ,
2023-09-24 18:33:52 -04:00
state : Arc < DiagnosticsState > ,
2023-07-11 17:10:43 -04:00
diagnostics_by_specifier :
Mutex < HashMap < ModuleSpecifier , DiagnosticsBySource > > ,
2022-01-24 18:04:24 -05:00
}
impl DiagnosticsPublisher {
2023-09-24 18:33:52 -04:00
pub fn new ( client : Client , state : Arc < DiagnosticsState > ) -> Self {
2022-01-24 18:04:24 -05:00
Self {
client ,
2023-09-24 12:59:42 -04:00
state ,
2023-07-11 17:10:43 -04:00
diagnostics_by_specifier : Default ::default ( ) ,
2022-01-24 18:04:24 -05:00
}
}
pub async fn publish (
& self ,
2023-07-11 17:10:43 -04:00
source : DiagnosticSource ,
2022-01-24 18:04:24 -05:00
diagnostics : DiagnosticVec ,
2023-08-02 16:57:25 -04:00
url_map : & LspUrlMap ,
2022-01-24 18:04:24 -05:00
token : & CancellationToken ,
2023-07-11 17:10:43 -04:00
) -> usize {
let mut diagnostics_by_specifier =
self . diagnostics_by_specifier . lock ( ) . await ;
let mut seen_specifiers = HashSet ::with_capacity ( diagnostics . len ( ) ) ;
let mut messages_sent = 0 ;
2023-07-11 16:36:39 -04:00
for record in diagnostics {
2022-01-24 18:04:24 -05:00
if token . is_cancelled ( ) {
2023-07-11 17:10:43 -04:00
return messages_sent ;
2022-01-24 18:04:24 -05:00
}
2023-07-11 17:10:43 -04:00
seen_specifiers . insert ( record . specifier . clone ( ) ) ;
let diagnostics_by_source = diagnostics_by_specifier
. entry ( record . specifier . clone ( ) )
2023-07-11 16:36:39 -04:00
. or_default ( ) ;
2023-07-11 17:10:43 -04:00
let version = record . versioned . version ;
let source_diagnostics = diagnostics_by_source . entry ( source ) . or_default ( ) ;
* source_diagnostics = record . versioned ;
// DO NOT filter these by version. We want to display even out
// of date diagnostics in order to prevent flickering. The user's
// lsp client will eventually catch up.
let all_specifier_diagnostics = diagnostics_by_source
. values ( )
. flat_map ( | d | & d . diagnostics )
. cloned ( )
. collect ::< Vec < _ > > ( ) ;
2022-01-24 18:04:24 -05:00
2023-09-24 18:33:52 -04:00
self
. state
. update ( & record . specifier , version , & all_specifier_diagnostics ) ;
2022-01-24 18:04:24 -05:00
self
. client
2023-03-15 10:34:23 -04:00
. when_outside_lsp_lock ( )
2023-07-11 16:36:39 -04:00
. publish_diagnostics (
2023-08-02 16:57:25 -04:00
url_map
. normalize_specifier ( & record . specifier )
. unwrap_or ( LspClientUrl ::new ( record . specifier ) ) ,
2023-07-11 17:10:43 -04:00
all_specifier_diagnostics ,
version ,
2023-07-11 16:36:39 -04:00
)
2022-01-24 18:04:24 -05:00
. await ;
2023-07-11 17:10:43 -04:00
messages_sent + = 1 ;
}
// now check all the specifiers to clean up any ones with old diagnostics
let mut specifiers_to_remove = Vec ::new ( ) ;
for ( specifier , diagnostics_by_source ) in
diagnostics_by_specifier . iter_mut ( )
{
if seen_specifiers . contains ( specifier ) {
continue ;
}
if token . is_cancelled ( ) {
break ;
}
let maybe_removed_value = diagnostics_by_source . remove ( & source ) ;
if diagnostics_by_source . is_empty ( ) {
specifiers_to_remove . push ( specifier . clone ( ) ) ;
if let Some ( removed_value ) = maybe_removed_value {
// clear out any diagnostics for this specifier
2023-09-24 18:33:52 -04:00
self . state . update ( specifier , removed_value . version , & [ ] ) ;
2023-07-11 17:10:43 -04:00
self
. client
. when_outside_lsp_lock ( )
. publish_diagnostics (
2023-08-02 16:57:25 -04:00
url_map
. normalize_specifier ( specifier )
. unwrap_or_else ( | _ | LspClientUrl ::new ( specifier . clone ( ) ) ) ,
2023-07-11 17:10:43 -04:00
Vec ::new ( ) ,
removed_value . version ,
)
. await ;
messages_sent + = 1 ;
}
}
}
// clean up specifiers with no diagnostics
for specifier in specifiers_to_remove {
diagnostics_by_specifier . remove ( & specifier ) ;
2022-01-24 18:04:24 -05:00
}
2023-07-11 17:10:43 -04:00
messages_sent
2022-01-24 18:04:24 -05:00
}
pub async fn clear ( & self ) {
2023-07-11 17:10:43 -04:00
let mut all_diagnostics = self . diagnostics_by_specifier . lock ( ) . await ;
2022-01-24 18:04:24 -05:00
all_diagnostics . clear ( ) ;
}
}
2021-05-11 10:43:00 -04:00
2023-07-11 17:10:43 -04:00
type DiagnosticMap = HashMap < ModuleSpecifier , VersionedDiagnostics > ;
2022-02-02 18:02:59 -05:00
#[ derive(Clone, Default, Debug) ]
struct TsDiagnosticsStore ( Arc < deno_core ::parking_lot ::Mutex < DiagnosticMap > > ) ;
impl TsDiagnosticsStore {
pub fn get (
& self ,
specifier : & ModuleSpecifier ,
document_version : Option < i32 > ,
) -> Vec < lsp ::Diagnostic > {
let ts_diagnostics = self . 0. lock ( ) ;
2023-07-11 16:36:39 -04:00
if let Some ( versioned ) = ts_diagnostics . get ( specifier ) {
2022-02-02 18:02:59 -05:00
// only get the diagnostics if they're up to date
2023-07-11 16:36:39 -04:00
if document_version = = versioned . version {
return versioned . diagnostics . clone ( ) ;
2022-02-02 18:02:59 -05:00
}
}
Vec ::new ( )
}
pub fn invalidate ( & self , specifiers : & [ ModuleSpecifier ] ) {
let mut ts_diagnostics = self . 0. lock ( ) ;
for specifier in specifiers {
ts_diagnostics . remove ( specifier ) ;
}
}
pub fn invalidate_all ( & self ) {
self . 0. lock ( ) . clear ( ) ;
}
fn update ( & self , diagnostics : & DiagnosticVec ) {
let mut stored_ts_diagnostics = self . 0. lock ( ) ;
* stored_ts_diagnostics = diagnostics
. iter ( )
2023-07-11 16:36:39 -04:00
. map ( | record | ( record . specifier . clone ( ) , record . versioned . clone ( ) ) )
2022-02-02 18:02:59 -05:00
. collect ( ) ;
}
}
2023-05-26 02:10:18 -04:00
pub fn should_send_diagnostic_batch_index_notifications ( ) -> bool {
crate ::args ::has_flag_env_var (
" DENO_DONT_USE_INTERNAL_LSP_DIAGNOSTIC_SYNC_FLAG " ,
)
}
#[ derive(Clone, Debug) ]
struct DiagnosticBatchCounter ( Option < Arc < AtomicUsize > > ) ;
impl Default for DiagnosticBatchCounter {
fn default ( ) -> Self {
if should_send_diagnostic_batch_index_notifications ( ) {
Self ( Some ( Default ::default ( ) ) )
} else {
Self ( None )
}
}
}
impl DiagnosticBatchCounter {
pub fn inc ( & self ) -> Option < usize > {
self
. 0
. as_ref ( )
. map ( | value | value . fetch_add ( 1 , std ::sync ::atomic ::Ordering ::SeqCst ) + 1 )
}
pub fn get ( & self ) -> Option < usize > {
self
. 0
. as_ref ( )
. map ( | value | value . load ( std ::sync ::atomic ::Ordering ::SeqCst ) )
}
}
#[ derive(Debug) ]
2023-07-11 17:10:43 -04:00
enum ChannelMessage {
Update ( ChannelUpdateMessage ) ,
Clear ,
}
#[ derive(Debug) ]
struct ChannelUpdateMessage {
2023-05-26 02:10:18 -04:00
message : DiagnosticServerUpdateMessage ,
batch_index : Option < usize > ,
}
2023-09-24 18:33:52 -04:00
#[ derive(Debug) ]
2023-09-24 12:59:42 -04:00
struct SpecifierState {
2023-09-24 18:33:52 -04:00
version : Option < i32 > ,
no_cache_diagnostics : Vec < lsp ::Diagnostic > ,
2023-09-24 12:59:42 -04:00
}
2023-09-24 18:33:52 -04:00
#[ derive(Debug, Default) ]
2023-09-24 12:59:42 -04:00
pub struct DiagnosticsState {
2023-09-24 18:33:52 -04:00
specifiers : RwLock < HashMap < ModuleSpecifier , SpecifierState > > ,
2023-09-24 12:59:42 -04:00
}
impl DiagnosticsState {
fn update (
2023-09-24 18:33:52 -04:00
& self ,
2023-09-24 12:59:42 -04:00
specifier : & ModuleSpecifier ,
version : Option < i32 > ,
diagnostics : & [ lsp ::Diagnostic ] ,
) {
2023-09-24 18:33:52 -04:00
let mut specifiers = self . specifiers . write ( ) ;
let current_version = specifiers . get ( specifier ) . and_then ( | s | s . version ) ;
match ( version , current_version ) {
( Some ( arg ) , Some ( existing ) ) if arg < existing = > return ,
_ = > { }
}
let mut no_cache_diagnostics = vec! [ ] ;
for diagnostic in diagnostics {
if diagnostic . code
= = Some ( lsp ::NumberOrString ::String ( " no-cache " . to_string ( ) ) )
| | diagnostic . code
= = Some ( lsp ::NumberOrString ::String ( " no-cache-npm " . to_string ( ) ) )
{
no_cache_diagnostics . push ( diagnostic . clone ( ) ) ;
}
2023-09-24 12:59:42 -04:00
}
2023-09-24 18:33:52 -04:00
specifiers . insert (
specifier . clone ( ) ,
SpecifierState {
version ,
no_cache_diagnostics ,
} ,
) ;
2023-09-24 12:59:42 -04:00
}
2023-09-24 18:33:52 -04:00
pub fn clear ( & self , specifier : & ModuleSpecifier ) {
self . specifiers . write ( ) . remove ( specifier ) ;
2023-09-24 12:59:42 -04:00
}
2023-09-24 18:33:52 -04:00
pub fn has_no_cache_diagnostics ( & self , specifier : & ModuleSpecifier ) -> bool {
2023-09-24 12:59:42 -04:00
self
. specifiers
2023-09-24 18:33:52 -04:00
. read ( )
2023-09-24 12:59:42 -04:00
. get ( specifier )
2023-09-24 18:33:52 -04:00
. map ( | s | ! s . no_cache_diagnostics . is_empty ( ) )
. unwrap_or ( false )
}
pub fn no_cache_diagnostics (
& self ,
specifier : & ModuleSpecifier ,
) -> Vec < lsp ::Diagnostic > {
self
. specifiers
. read ( )
. get ( specifier )
. map ( | s | s . no_cache_diagnostics . clone ( ) )
. unwrap_or_default ( )
2023-09-24 12:59:42 -04:00
}
}
2022-01-17 17:09:43 -05:00
#[ derive(Debug) ]
2022-03-23 09:54:22 -04:00
pub struct DiagnosticsServer {
2023-05-26 02:10:18 -04:00
channel : Option < mpsc ::UnboundedSender < ChannelMessage > > ,
2022-02-02 18:02:59 -05:00
ts_diagnostics : TsDiagnosticsStore ,
2022-01-17 17:09:43 -05:00
client : Client ,
performance : Arc < Performance > ,
ts_server : Arc < TsServer > ,
2023-05-26 02:10:18 -04:00
batch_counter : DiagnosticBatchCounter ,
2023-09-24 18:33:52 -04:00
state : Arc < DiagnosticsState > ,
2021-05-11 10:43:00 -04:00
}
2021-03-09 21:41:35 -05:00
impl DiagnosticsServer {
2022-01-17 17:09:43 -05:00
pub fn new (
client : Client ,
performance : Arc < Performance > ,
ts_server : Arc < TsServer > ,
2023-09-24 18:33:52 -04:00
state : Arc < DiagnosticsState > ,
2022-01-17 17:09:43 -05:00
) -> Self {
DiagnosticsServer {
channel : Default ::default ( ) ,
2022-01-24 15:30:01 -05:00
ts_diagnostics : Default ::default ( ) ,
2022-01-17 17:09:43 -05:00
client ,
performance ,
ts_server ,
2023-05-26 02:10:18 -04:00
batch_counter : Default ::default ( ) ,
2023-09-24 18:33:52 -04:00
state ,
2022-01-17 17:09:43 -05:00
}
}
2022-03-23 09:54:22 -04:00
pub fn get_ts_diagnostics (
2021-05-11 10:43:00 -04:00
& self ,
specifier : & ModuleSpecifier ,
2022-01-24 15:30:01 -05:00
document_version : Option < i32 > ,
2021-05-11 10:43:00 -04:00
) -> Vec < lsp ::Diagnostic > {
2022-02-02 18:02:59 -05:00
self . ts_diagnostics . get ( specifier , document_version )
2021-05-11 10:43:00 -04:00
}
2022-03-23 09:54:22 -04:00
pub fn invalidate ( & self , specifiers : & [ ModuleSpecifier ] ) {
2022-02-02 18:02:59 -05:00
self . ts_diagnostics . invalidate ( specifiers ) ;
2021-03-09 21:41:35 -05:00
}
2022-03-23 09:54:22 -04:00
pub fn invalidate_all ( & self ) {
2022-02-02 18:02:59 -05:00
self . ts_diagnostics . invalidate_all ( ) ;
2023-07-11 17:10:43 -04:00
if let Some ( tx ) = & self . channel {
let _ = tx . send ( ChannelMessage ::Clear ) ;
}
2021-07-25 01:33:42 -04:00
}
2022-02-23 16:01:20 -05:00
#[ allow(unused_must_use) ]
2022-03-23 09:54:22 -04:00
pub fn start ( & mut self ) {
2023-05-26 02:10:18 -04:00
let ( tx , mut rx ) = mpsc ::unbounded_channel ::< ChannelMessage > ( ) ;
2021-05-11 10:43:00 -04:00
self . channel = Some ( tx ) ;
2022-01-17 17:09:43 -05:00
let client = self . client . clone ( ) ;
2023-09-24 12:59:42 -04:00
let state = self . state . clone ( ) ;
2022-01-17 17:09:43 -05:00
let performance = self . performance . clone ( ) ;
2022-02-02 18:02:59 -05:00
let ts_diagnostics_store = self . ts_diagnostics . clone ( ) ;
2022-01-17 17:09:43 -05:00
let ts_server = self . ts_server . clone ( ) ;
2021-03-09 21:41:35 -05:00
let _join_handle = thread ::spawn ( move | | {
let runtime = create_basic_runtime ( ) ;
runtime . block_on ( async {
2022-01-24 15:30:01 -05:00
let mut token = CancellationToken ::new ( ) ;
2023-05-14 17:40:01 -04:00
let mut ts_handle : Option < JoinHandle < ( ) > > = None ;
let mut lint_handle : Option < JoinHandle < ( ) > > = None ;
let mut deps_handle : Option < JoinHandle < ( ) > > = None ;
2023-07-11 17:10:43 -04:00
let diagnostics_publisher =
2023-09-24 12:59:42 -04:00
Arc ::new ( DiagnosticsPublisher ::new ( client . clone ( ) , state . clone ( ) ) ) ;
2021-03-09 21:41:35 -05:00
2021-03-18 16:26:41 -04:00
loop {
2022-01-24 15:30:01 -05:00
match rx . recv ( ) . await {
// channel has closed
None = > break ,
2023-05-26 02:10:18 -04:00
Some ( message ) = > {
2023-07-11 17:10:43 -04:00
let message = match message {
ChannelMessage ::Update ( message ) = > message ,
ChannelMessage ::Clear = > {
token . cancel ( ) ;
token = CancellationToken ::new ( ) ;
diagnostics_publisher . clear ( ) . await ;
continue ;
}
} ;
let ChannelUpdateMessage {
2023-05-26 02:10:18 -04:00
message :
DiagnosticServerUpdateMessage {
snapshot ,
config ,
lint_options ,
2023-08-02 16:57:25 -04:00
url_map ,
2023-05-26 02:10:18 -04:00
} ,
batch_index ,
} = message ;
2023-08-02 16:57:25 -04:00
let url_map = Arc ::new ( url_map ) ;
2023-05-26 02:10:18 -04:00
2022-01-24 15:30:01 -05:00
// cancel the previous run
token . cancel ( ) ;
token = CancellationToken ::new ( ) ;
2021-03-18 16:26:41 -04:00
2022-01-24 15:30:01 -05:00
let previous_ts_handle = ts_handle . take ( ) ;
2023-05-14 17:40:01 -04:00
ts_handle = Some ( spawn ( {
2022-01-24 15:30:01 -05:00
let performance = performance . clone ( ) ;
2022-01-24 18:04:24 -05:00
let diagnostics_publisher = diagnostics_publisher . clone ( ) ;
2022-01-24 15:30:01 -05:00
let ts_server = ts_server . clone ( ) ;
let token = token . clone ( ) ;
2022-02-02 18:02:59 -05:00
let ts_diagnostics_store = ts_diagnostics_store . clone ( ) ;
2022-01-24 15:30:01 -05:00
let snapshot = snapshot . clone ( ) ;
let config = config . clone ( ) ;
2023-08-02 16:57:25 -04:00
let url_map = url_map . clone ( ) ;
2022-01-24 15:30:01 -05:00
async move {
if let Some ( previous_handle ) = previous_ts_handle {
// Wait on the previous run to complete in order to prevent
// multiple threads queueing up a lot of tsc requests.
// Do not race this with cancellation because we want a
// chain of events to wait for all the previous diagnostics to complete
previous_handle . await ;
}
// Debounce timer delay. 150ms between keystrokes is about 45 WPM, so we
// want something that is longer than that, but not too long to
// introduce detectable UI delay; 200ms is a decent compromise.
const DELAY : Duration = Duration ::from_millis ( 200 ) ;
tokio ::select! {
_ = token . cancelled ( ) = > { return ; }
_ = tokio ::time ::sleep ( DELAY ) = > { }
} ;
2023-11-30 21:54:59 -05:00
let mark = performance . mark ( " lsp.update_diagnostics_ts " ) ;
2022-01-29 17:50:15 -05:00
let diagnostics = generate_ts_diagnostics (
snapshot . clone ( ) ,
& config ,
& ts_server ,
2022-02-02 09:25:22 -05:00
token . clone ( ) ,
2022-01-29 17:50:15 -05:00
)
. await
. map_err ( | err | {
2023-12-03 17:07:40 -05:00
if ! token . is_cancelled ( ) {
error! (
" Error generating TypeScript diagnostics: {} " ,
err
) ;
}
2022-01-29 17:50:15 -05:00
} )
. unwrap_or_default ( ) ;
2022-01-24 15:30:01 -05:00
2023-07-11 17:10:43 -04:00
let mut messages_len = 0 ;
2022-01-24 15:30:01 -05:00
if ! token . is_cancelled ( ) {
2022-02-02 18:02:59 -05:00
ts_diagnostics_store . update ( & diagnostics ) ;
2023-07-11 17:10:43 -04:00
messages_len = diagnostics_publisher
2023-08-02 16:57:25 -04:00
. publish (
DiagnosticSource ::Ts ,
diagnostics ,
& url_map ,
& token ,
)
2023-07-11 17:10:43 -04:00
. await ;
2022-01-24 18:04:24 -05:00
if ! token . is_cancelled ( ) {
performance . measure ( mark ) ;
2022-01-24 15:30:01 -05:00
}
}
2023-05-26 02:10:18 -04:00
if let Some ( batch_index ) = batch_index {
diagnostics_publisher
. client
. send_diagnostic_batch_notification (
DiagnosticBatchNotificationParams {
batch_index ,
messages_len ,
} ,
) ;
}
2022-01-24 15:30:01 -05:00
}
} ) ) ;
let previous_deps_handle = deps_handle . take ( ) ;
2023-05-14 17:40:01 -04:00
deps_handle = Some ( spawn ( {
2022-01-24 15:30:01 -05:00
let performance = performance . clone ( ) ;
2022-01-24 18:04:24 -05:00
let diagnostics_publisher = diagnostics_publisher . clone ( ) ;
2022-01-24 15:30:01 -05:00
let token = token . clone ( ) ;
let snapshot = snapshot . clone ( ) ;
let config = config . clone ( ) ;
2023-08-02 16:57:25 -04:00
let url_map = url_map . clone ( ) ;
2022-01-24 15:30:01 -05:00
async move {
if let Some ( previous_handle ) = previous_deps_handle {
previous_handle . await ;
}
2023-11-30 21:54:59 -05:00
let mark = performance . mark ( " lsp.update_diagnostics_deps " ) ;
2023-07-11 17:10:43 -04:00
let diagnostics = spawn_blocking ( {
let token = token . clone ( ) ;
move | | generate_deno_diagnostics ( & snapshot , & config , token )
} )
. await
. unwrap ( ) ;
2022-01-24 15:30:01 -05:00
2023-07-11 17:10:43 -04:00
let mut messages_len = 0 ;
2023-05-26 00:31:54 -04:00
if ! token . is_cancelled ( ) {
2023-07-11 17:10:43 -04:00
messages_len = diagnostics_publisher
2023-08-02 16:57:25 -04:00
. publish (
DiagnosticSource ::Deno ,
diagnostics ,
& url_map ,
& token ,
)
2023-07-11 17:10:43 -04:00
. await ;
2023-05-26 02:10:18 -04:00
if ! token . is_cancelled ( ) {
performance . measure ( mark ) ;
}
}
if let Some ( batch_index ) = batch_index {
diagnostics_publisher
. client
. send_diagnostic_batch_notification (
DiagnosticBatchNotificationParams {
batch_index ,
messages_len ,
} ,
) ;
2022-01-24 15:30:01 -05:00
}
}
} ) ) ;
let previous_lint_handle = lint_handle . take ( ) ;
2023-05-14 17:40:01 -04:00
lint_handle = Some ( spawn ( {
2022-01-24 15:30:01 -05:00
let performance = performance . clone ( ) ;
2022-01-24 18:04:24 -05:00
let diagnostics_publisher = diagnostics_publisher . clone ( ) ;
2022-01-24 15:30:01 -05:00
let token = token . clone ( ) ;
let snapshot = snapshot . clone ( ) ;
let config = config . clone ( ) ;
2023-08-02 16:57:25 -04:00
let url_map = url_map . clone ( ) ;
2022-01-24 15:30:01 -05:00
async move {
if let Some ( previous_handle ) = previous_lint_handle {
previous_handle . await ;
}
2023-11-30 21:54:59 -05:00
let mark = performance . mark ( " lsp.update_diagnostics_lint " ) ;
2023-07-11 17:10:43 -04:00
let diagnostics = spawn_blocking ( {
let token = token . clone ( ) ;
move | | {
generate_lint_diagnostics (
& snapshot ,
& config ,
& lint_options ,
token ,
)
}
} )
. await
. unwrap ( ) ;
2022-01-24 15:30:01 -05:00
2023-07-11 17:10:43 -04:00
let mut messages_len = 0 ;
2023-05-26 00:31:54 -04:00
if ! token . is_cancelled ( ) {
2023-07-11 17:10:43 -04:00
messages_len = diagnostics_publisher
2023-08-02 16:57:25 -04:00
. publish (
DiagnosticSource ::Lint ,
diagnostics ,
& url_map ,
& token ,
)
2023-07-11 17:10:43 -04:00
. await ;
2023-05-26 02:10:18 -04:00
if ! token . is_cancelled ( ) {
performance . measure ( mark ) ;
}
}
if let Some ( batch_index ) = batch_index {
diagnostics_publisher
. client
. send_diagnostic_batch_notification (
DiagnosticBatchNotificationParams {
batch_index ,
messages_len ,
} ,
) ;
2022-01-24 15:30:01 -05:00
}
}
} ) ) ;
2021-03-09 21:41:35 -05:00
}
}
}
} )
} ) ;
}
2023-05-26 02:10:18 -04:00
pub fn latest_batch_index ( & self ) -> Option < usize > {
self . batch_counter . get ( )
}
2022-03-23 09:54:22 -04:00
pub fn update (
2022-02-02 18:02:59 -05:00
& self ,
2023-05-26 02:10:18 -04:00
message : DiagnosticServerUpdateMessage ,
2022-02-02 18:02:59 -05:00
) -> Result < ( ) , AnyError > {
// todo(dsherret): instead of queuing up messages, it would be better to
// instead only store the latest message (ex. maybe using a
// tokio::sync::watch::channel)
2021-05-11 10:43:00 -04:00
if let Some ( tx ) = & self . channel {
2023-07-11 17:10:43 -04:00
tx . send ( ChannelMessage ::Update ( ChannelUpdateMessage {
2023-05-26 02:10:18 -04:00
message ,
batch_index : self . batch_counter . inc ( ) ,
2023-07-11 17:10:43 -04:00
} ) )
2023-05-26 02:10:18 -04:00
. map_err ( | err | err . into ( ) )
2021-05-07 07:05:32 -04:00
} else {
2021-05-11 10:43:00 -04:00
Err ( anyhow! ( " diagnostics server not started " ) )
2021-05-07 07:05:32 -04:00
}
2020-12-07 05:46:39 -05:00
}
2020-12-21 08:44:26 -05:00
}
2022-11-25 18:29:48 -05:00
impl < ' a > From < & ' a crate ::tsc ::DiagnosticCategory > for lsp ::DiagnosticSeverity {
fn from ( category : & ' a crate ::tsc ::DiagnosticCategory ) -> Self {
2020-12-21 08:44:26 -05:00
match category {
2022-11-25 18:29:48 -05:00
crate ::tsc ::DiagnosticCategory ::Error = > lsp ::DiagnosticSeverity ::ERROR ,
crate ::tsc ::DiagnosticCategory ::Warning = > {
2021-11-24 20:10:12 -05:00
lsp ::DiagnosticSeverity ::WARNING
2020-12-21 08:44:26 -05:00
}
2022-11-25 18:29:48 -05:00
crate ::tsc ::DiagnosticCategory ::Suggestion = > {
2021-11-24 20:10:12 -05:00
lsp ::DiagnosticSeverity ::HINT
2020-12-21 08:44:26 -05:00
}
2022-11-25 18:29:48 -05:00
crate ::tsc ::DiagnosticCategory ::Message = > {
2021-11-24 20:10:12 -05:00
lsp ::DiagnosticSeverity ::INFORMATION
2020-12-21 08:44:26 -05:00
}
}
}
}
2022-11-25 18:29:48 -05:00
impl < ' a > From < & ' a crate ::tsc ::Position > for lsp ::Position {
fn from ( pos : & ' a crate ::tsc ::Position ) -> Self {
2020-12-21 08:44:26 -05:00
Self {
line : pos . line as u32 ,
character : pos . character as u32 ,
}
2020-12-07 05:46:39 -05:00
}
2020-12-21 08:44:26 -05:00
}
2020-12-07 05:46:39 -05:00
2022-11-25 18:29:48 -05:00
fn get_diagnostic_message ( diagnostic : & crate ::tsc ::Diagnostic ) -> String {
2020-12-07 05:46:39 -05:00
if let Some ( message ) = diagnostic . message_text . clone ( ) {
message
} else if let Some ( message_chain ) = diagnostic . message_chain . clone ( ) {
message_chain . format_message ( 0 )
} else {
" [missing message] " . to_string ( )
}
}
2021-05-11 10:43:00 -04:00
fn to_lsp_range (
2022-11-25 18:29:48 -05:00
start : & crate ::tsc ::Position ,
end : & crate ::tsc ::Position ,
2021-05-11 10:43:00 -04:00
) -> lsp ::Range {
lsp ::Range {
start : start . into ( ) ,
end : end . into ( ) ,
}
}
2020-12-07 05:46:39 -05:00
fn to_lsp_related_information (
2022-11-25 18:29:48 -05:00
related_information : & Option < Vec < crate ::tsc ::Diagnostic > > ,
2021-01-29 14:34:33 -05:00
) -> Option < Vec < lsp ::DiagnosticRelatedInformation > > {
2021-03-25 14:17:37 -04:00
related_information . as_ref ( ) . map ( | related | {
related
. iter ( )
. filter_map ( | ri | {
2023-09-24 03:18:51 -04:00
if let ( Some ( file_name ) , Some ( start ) , Some ( end ) ) =
( & ri . file_name , & ri . start , & ri . end )
2021-03-25 14:17:37 -04:00
{
2023-09-24 03:18:51 -04:00
let uri = lsp ::Url ::parse ( file_name ) . unwrap ( ) ;
2021-03-25 14:17:37 -04:00
Some ( lsp ::DiagnosticRelatedInformation {
location : lsp ::Location {
uri ,
range : to_lsp_range ( start , end ) ,
} ,
2021-07-30 09:03:41 -04:00
message : get_diagnostic_message ( ri ) ,
2021-03-25 14:17:37 -04:00
} )
} else {
None
}
} )
. collect ( )
} )
2020-12-07 05:46:39 -05:00
}
fn ts_json_to_diagnostics (
2022-11-25 18:29:48 -05:00
diagnostics : Vec < crate ::tsc ::Diagnostic > ,
2021-01-29 14:34:33 -05:00
) -> Vec < lsp ::Diagnostic > {
2021-01-22 05:03:16 -05:00
diagnostics
. iter ( )
. filter_map ( | d | {
if let ( Some ( start ) , Some ( end ) ) = ( & d . start , & d . end ) {
2021-01-29 14:34:33 -05:00
Some ( lsp ::Diagnostic {
2021-01-22 05:03:16 -05:00
range : to_lsp_range ( start , end ) ,
severity : Some ( ( & d . category ) . into ( ) ) ,
2021-01-29 14:34:33 -05:00
code : Some ( lsp ::NumberOrString ::Number ( d . code as i32 ) ) ,
2021-01-22 05:03:16 -05:00
code_description : None ,
2023-07-11 17:10:43 -04:00
source : Some ( DiagnosticSource ::Ts . as_lsp_source ( ) . to_string ( ) ) ,
2021-01-22 05:03:16 -05:00
message : get_diagnostic_message ( d ) ,
related_information : to_lsp_related_information (
& d . related_information ,
) ,
tags : match d . code {
// These are codes that indicate the variable is unused.
2021-11-17 21:05:20 -05:00
2695 | 6133 | 6138 | 6192 | 6196 | 6198 | 6199 | 6205 | 7027
2021-11-24 20:10:12 -05:00
| 7028 = > Some ( vec! [ lsp ::DiagnosticTag ::UNNECESSARY ] ) ,
2021-11-17 21:05:20 -05:00
// These are codes that indicated the variable is deprecated.
2021-11-24 20:10:12 -05:00
2789 | 6385 | 6387 = > Some ( vec! [ lsp ::DiagnosticTag ::DEPRECATED ] ) ,
2021-01-22 05:03:16 -05:00
_ = > None ,
} ,
data : None ,
} )
} else {
None
}
} )
. collect ( )
2020-12-07 05:46:39 -05:00
}
2023-07-11 17:10:43 -04:00
fn generate_lint_diagnostics (
2021-05-11 10:43:00 -04:00
snapshot : & language_server ::StateSnapshot ,
2022-01-19 17:10:14 -05:00
config : & ConfigSnapshot ,
2023-01-07 15:22:09 -05:00
lint_options : & LintOptions ,
2022-01-24 15:30:01 -05:00
token : CancellationToken ,
) -> DiagnosticVec {
2023-03-29 16:25:48 -04:00
let documents = snapshot
. documents
. documents ( DocumentsFilter ::OpenDiagnosable ) ;
2023-01-07 15:22:09 -05:00
let lint_rules = get_configured_rules ( lint_options . rules . clone ( ) ) ;
2022-01-24 15:30:01 -05:00
let mut diagnostics_vec = Vec ::new ( ) ;
2023-10-24 16:27:27 -04:00
for document in documents {
let settings =
config . workspace_settings_for_specifier ( document . specifier ( ) ) ;
if ! settings . lint {
continue ;
}
// exit early if cancelled
if token . is_cancelled ( ) {
break ;
}
// ignore any npm package files
if let Some ( npm ) = & snapshot . npm {
if npm . node_resolver . in_npm_package ( document . specifier ( ) ) {
continue ;
2022-10-21 11:20:18 -04:00
}
2021-01-22 05:03:16 -05:00
}
2023-10-24 16:27:27 -04:00
let version = document . maybe_lsp_version ( ) ;
diagnostics_vec . push ( DiagnosticRecord {
specifier : document . specifier ( ) . clone ( ) ,
versioned : VersionedDiagnostics {
version ,
diagnostics : generate_document_lint_diagnostics (
config ,
lint_options ,
lint_rules . clone ( ) ,
& document ,
) ,
} ,
} ) ;
2022-01-24 15:30:01 -05:00
}
diagnostics_vec
2021-05-11 10:43:00 -04:00
}
2022-01-29 17:50:15 -05:00
fn generate_document_lint_diagnostics (
config : & ConfigSnapshot ,
2023-01-07 15:22:09 -05:00
lint_options : & LintOptions ,
2023-03-02 16:50:17 -05:00
lint_rules : Vec < & 'static dyn LintRule > ,
2022-01-29 17:50:15 -05:00
document : & Document ,
) -> Vec < lsp ::Diagnostic > {
if ! config . specifier_enabled ( document . specifier ( ) ) {
return Vec ::new ( ) ;
}
2023-01-07 15:22:09 -05:00
if ! lint_options . files . matches_specifier ( document . specifier ( ) ) {
return Vec ::new ( ) ;
2022-01-29 17:50:15 -05:00
}
match document . maybe_parsed_source ( ) {
Some ( Ok ( parsed_source ) ) = > {
2023-01-07 15:22:09 -05:00
if let Ok ( references ) =
analysis ::get_lint_references ( & parsed_source , lint_rules )
{
2022-01-29 17:50:15 -05:00
references
. into_iter ( )
. map ( | r | r . to_diagnostic ( ) )
. collect ::< Vec < _ > > ( )
} else {
Vec ::new ( )
}
}
Some ( Err ( _ ) ) = > Vec ::new ( ) ,
None = > {
error! ( " Missing file contents for: {} " , document . specifier ( ) ) ;
Vec ::new ( )
}
}
}
2021-05-11 10:43:00 -04:00
async fn generate_ts_diagnostics (
2021-11-18 13:50:24 -05:00
snapshot : Arc < language_server ::StateSnapshot > ,
2022-01-29 17:50:15 -05:00
config : & ConfigSnapshot ,
2021-05-11 10:43:00 -04:00
ts_server : & tsc ::TsServer ,
2022-02-02 09:25:22 -05:00
token : CancellationToken ,
2021-05-11 10:43:00 -04:00
) -> Result < DiagnosticVec , AnyError > {
let mut diagnostics_vec = Vec ::new ( ) ;
2022-01-24 15:30:01 -05:00
let specifiers = snapshot
. documents
2023-03-29 16:25:48 -04:00
. documents ( DocumentsFilter ::OpenDiagnosable )
2023-01-16 15:27:41 -05:00
. into_iter ( )
. map ( | d | d . specifier ( ) . clone ( ) ) ;
2022-01-29 17:50:15 -05:00
let ( enabled_specifiers , disabled_specifiers ) = specifiers
2023-01-16 15:27:41 -05:00
. into_iter ( )
2022-01-29 17:50:15 -05:00
. partition ::< Vec < _ > , _ > ( | s | config . specifier_enabled ( s ) ) ;
2023-05-12 19:07:40 -04:00
let ts_diagnostics_map = if ! enabled_specifiers . is_empty ( ) {
2022-02-02 09:25:22 -05:00
ts_server
2023-05-12 19:07:40 -04:00
. get_diagnostics ( snapshot . clone ( ) , enabled_specifiers , token )
2022-02-02 09:25:22 -05:00
. await ?
2022-01-29 17:50:15 -05:00
} else {
Default ::default ( )
} ;
for ( specifier_str , ts_json_diagnostics ) in ts_diagnostics_map {
let specifier = resolve_url ( & specifier_str ) ? ;
let version = snapshot
. documents
. get ( & specifier )
2022-02-24 20:03:12 -05:00
. and_then ( | d | d . maybe_lsp_version ( ) ) ;
2022-01-29 17:50:15 -05:00
// check if the specifier is enabled again just in case TS returns us
// diagnostics for a disabled specifier
let ts_diagnostics = if config . specifier_enabled ( & specifier ) {
ts_json_to_diagnostics ( ts_json_diagnostics )
} else {
Vec ::new ( )
} ;
2023-07-11 16:36:39 -04:00
diagnostics_vec . push ( DiagnosticRecord {
specifier ,
versioned : VersionedDiagnostics {
version ,
diagnostics : ts_diagnostics ,
} ,
} ) ;
2022-01-29 17:50:15 -05:00
}
// add an empty diagnostic publish for disabled specifiers in order
// to clear those diagnostics if they exist
for specifier in disabled_specifiers {
let version = snapshot
. documents
. get ( & specifier )
2022-02-24 20:03:12 -05:00
. and_then ( | d | d . maybe_lsp_version ( ) ) ;
2023-07-11 16:36:39 -04:00
diagnostics_vec . push ( DiagnosticRecord {
specifier ,
versioned : VersionedDiagnostics {
version ,
diagnostics : Vec ::new ( ) ,
} ,
} ) ;
2020-12-07 05:46:39 -05:00
}
2021-05-11 10:43:00 -04:00
Ok ( diagnostics_vec )
2020-12-07 05:46:39 -05:00
}
2020-12-24 05:53:03 -05:00
2022-02-04 02:14:57 -05:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
2023-09-24 18:33:52 -04:00
pub struct DiagnosticDataSpecifier {
2022-02-04 02:14:57 -05:00
pub specifier : ModuleSpecifier ,
}
2023-01-24 15:14:49 -05:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct DiagnosticDataStrSpecifier {
pub specifier : String ,
}
2022-02-04 02:14:57 -05:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct DiagnosticDataRedirect {
pub redirect : ModuleSpecifier ,
}
2021-10-28 19:56:01 -04:00
2022-07-13 21:12:18 -04:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct DiagnosticDataImportMapRemap {
pub from : String ,
pub to : String ,
}
2022-02-04 02:14:57 -05:00
/// An enum which represents diagnostic errors which originate from Deno itself.
2022-03-23 09:54:22 -04:00
pub enum DenoDiagnostic {
2022-06-17 11:41:28 -04:00
/// A `x-deno-warning` is associated with the specifier and should be displayed
2022-02-04 02:14:57 -05:00
/// as a warning to the user.
DenoWarn ( String ) ,
2022-07-13 21:12:18 -04:00
/// An informational diagnostic that indicates an existing specifier can be
/// remapped to an import map import specifier.
ImportMapRemap { from : String , to : String } ,
2022-02-04 02:14:57 -05:00
/// The import assertion type is incorrect.
2023-09-07 09:09:16 -04:00
InvalidAttributeType ( String ) ,
/// A module requires an attribute type to be a valid import.
NoAttributeType ,
2022-02-04 02:14:57 -05:00
/// A remote module was not found in the cache.
NoCache ( ModuleSpecifier ) ,
2022-10-21 11:20:18 -04:00
/// A remote npm package reference was not found in the cache.
2023-08-21 05:53:52 -04:00
NoCacheNpm ( PackageReq , ModuleSpecifier ) ,
2022-02-04 02:14:57 -05:00
/// A local module was not found on the local file system.
NoLocal ( ModuleSpecifier ) ,
/// The specifier resolved to a remote specifier that was redirected to
/// another specifier.
Redirect {
from : ModuleSpecifier ,
to : ModuleSpecifier ,
} ,
/// An error occurred when resolving the specifier string.
ResolutionError ( deno_graph ::ResolutionError ) ,
2023-01-24 09:05:54 -05:00
/// Invalid `node:` specifier.
InvalidNodeSpecifier ( ModuleSpecifier ) ,
2023-10-20 00:02:08 -04:00
/// Bare specifier is used for `node:` specifier
BareNodeSpecifier ( String ) ,
2022-02-04 02:14:57 -05:00
}
impl DenoDiagnostic {
fn code ( & self ) -> & str {
match self {
Self ::DenoWarn ( _ ) = > " deno-warn " ,
2022-07-13 21:12:18 -04:00
Self ::ImportMapRemap { .. } = > " import-map-remap " ,
2023-09-07 09:09:16 -04:00
Self ::InvalidAttributeType ( _ ) = > " invalid-attribute-type " ,
Self ::NoAttributeType = > " no-attribute-type " ,
2022-02-04 02:14:57 -05:00
Self ::NoCache ( _ ) = > " no-cache " ,
2022-10-21 11:20:18 -04:00
Self ::NoCacheNpm ( _ , _ ) = > " no-cache-npm " ,
2022-02-04 02:14:57 -05:00
Self ::NoLocal ( _ ) = > " no-local " ,
Self ::Redirect { .. } = > " redirect " ,
2023-01-27 17:36:23 -05:00
Self ::ResolutionError ( err ) = > {
if graph_util ::get_resolution_error_bare_node_specifier ( err ) . is_some ( ) {
" import-node-prefix-missing "
} else {
match err {
ResolutionError ::InvalidDowngrade { .. } = > " invalid-downgrade " ,
ResolutionError ::InvalidLocalImport { .. } = > {
" invalid-local-import "
}
ResolutionError ::InvalidSpecifier { error , .. } = > match error {
SpecifierError ::ImportPrefixMissing ( _ , _ ) = > {
" import-prefix-missing "
}
SpecifierError ::InvalidUrl ( _ ) = > " invalid-url " ,
} ,
ResolutionError ::ResolverError { .. } = > " resolver-error " ,
}
}
}
2023-01-24 09:05:54 -05:00
Self ::InvalidNodeSpecifier ( _ ) = > " resolver-error " ,
2023-10-20 00:02:08 -04:00
Self ::BareNodeSpecifier ( _ ) = > " import-node-prefix-missing " ,
2021-10-28 19:56:01 -04:00
}
2022-02-04 02:14:57 -05:00
}
/// A "static" method which for a diagnostic that originated from the
/// structure returns a code action which can resolve the diagnostic.
2022-03-23 09:54:22 -04:00
pub fn get_code_action (
2022-02-04 02:14:57 -05:00
specifier : & ModuleSpecifier ,
diagnostic : & lsp ::Diagnostic ,
) -> Result < lsp ::CodeAction , AnyError > {
if let Some ( lsp ::NumberOrString ::String ( code ) ) = & diagnostic . code {
let code_action = match code . as_str ( ) {
2022-07-13 21:12:18 -04:00
" import-map-remap " = > {
let data = diagnostic
. data
. clone ( )
. ok_or_else ( | | anyhow! ( " Diagnostic is missing data " ) ) ? ;
let DiagnosticDataImportMapRemap { from , to } =
serde_json ::from_value ( data ) ? ;
lsp ::CodeAction {
2023-01-27 10:43:16 -05:00
title : format ! ( " Update \" {from} \" to \" {to} \" to use import map. " ) ,
2022-07-13 21:12:18 -04:00
kind : Some ( lsp ::CodeActionKind ::QUICKFIX ) ,
diagnostics : Some ( vec! [ diagnostic . clone ( ) ] ) ,
edit : Some ( lsp ::WorkspaceEdit {
changes : Some ( HashMap ::from ( [ (
specifier . clone ( ) ,
vec! [ lsp ::TextEdit {
2023-01-27 10:43:16 -05:00
new_text : format ! ( " \" {to} \" " ) ,
2022-07-13 21:12:18 -04:00
range : diagnostic . range ,
} ] ,
) ] ) ) ,
.. Default ::default ( )
} ) ,
.. Default ::default ( )
}
}
2023-09-07 09:09:16 -04:00
" no-attribute-type " = > lsp ::CodeAction {
title : " Insert import attribute. " . to_string ( ) ,
2022-02-04 02:14:57 -05:00
kind : Some ( lsp ::CodeActionKind ::QUICKFIX ) ,
diagnostics : Some ( vec! [ diagnostic . clone ( ) ] ) ,
edit : Some ( lsp ::WorkspaceEdit {
changes : Some ( HashMap ::from ( [ (
specifier . clone ( ) ,
vec! [ lsp ::TextEdit {
2023-09-07 09:09:16 -04:00
new_text : " with { type: \" json \" } " . to_string ( ) ,
2022-02-04 02:14:57 -05:00
range : lsp ::Range {
start : diagnostic . range . end ,
end : diagnostic . range . end ,
} ,
} ] ,
) ] ) ) ,
.. Default ::default ( )
} ) ,
.. Default ::default ( )
} ,
2023-07-01 18:52:30 -04:00
" no-cache " | " no-cache-npm " = > {
2022-02-04 02:14:57 -05:00
let data = diagnostic
. data
. clone ( )
. ok_or_else ( | | anyhow! ( " Diagnostic is missing data " ) ) ? ;
let data : DiagnosticDataSpecifier = serde_json ::from_value ( data ) ? ;
lsp ::CodeAction {
2023-09-24 18:33:52 -04:00
title : format ! (
" Cache \" {} \" and its dependencies. " ,
data . specifier
) ,
2022-02-04 02:14:57 -05:00
kind : Some ( lsp ::CodeActionKind ::QUICKFIX ) ,
diagnostics : Some ( vec! [ diagnostic . clone ( ) ] ) ,
command : Some ( lsp ::Command {
title : " " . to_string ( ) ,
command : " deno.cache " . to_string ( ) ,
2023-09-05 11:36:35 -04:00
arguments : Some ( vec! [ json! ( [ data . specifier ] ) , json! ( & specifier ) ] ) ,
2022-02-04 02:14:57 -05:00
} ) ,
.. Default ::default ( )
}
}
" redirect " = > {
let data = diagnostic
. data
. clone ( )
. ok_or_else ( | | anyhow! ( " Diagnostic is missing data " ) ) ? ;
let data : DiagnosticDataRedirect = serde_json ::from_value ( data ) ? ;
lsp ::CodeAction {
title : " Update specifier to its redirected specifier. " . to_string ( ) ,
kind : Some ( lsp ::CodeActionKind ::QUICKFIX ) ,
diagnostics : Some ( vec! [ diagnostic . clone ( ) ] ) ,
edit : Some ( lsp ::WorkspaceEdit {
changes : Some ( HashMap ::from ( [ (
specifier . clone ( ) ,
vec! [ lsp ::TextEdit {
new_text : format ! ( " \" {} \" " , data . redirect ) ,
range : diagnostic . range ,
} ] ,
) ] ) ) ,
.. Default ::default ( )
} ) ,
.. Default ::default ( )
}
}
2023-01-27 17:36:23 -05:00
" import-node-prefix-missing " = > {
2023-01-24 15:14:49 -05:00
let data = diagnostic
. data
. clone ( )
. ok_or_else ( | | anyhow! ( " Diagnostic is missing data " ) ) ? ;
let data : DiagnosticDataStrSpecifier = serde_json ::from_value ( data ) ? ;
lsp ::CodeAction {
title : format ! ( " Update specifier to node:{} " , data . specifier ) ,
kind : Some ( lsp ::CodeActionKind ::QUICKFIX ) ,
diagnostics : Some ( vec! [ diagnostic . clone ( ) ] ) ,
edit : Some ( lsp ::WorkspaceEdit {
changes : Some ( HashMap ::from ( [ (
specifier . clone ( ) ,
vec! [ lsp ::TextEdit {
new_text : format ! ( " \" node:{} \" " , data . specifier ) ,
range : diagnostic . range ,
} ] ,
) ] ) ) ,
.. Default ::default ( )
} ) ,
.. Default ::default ( )
}
}
2022-02-04 02:14:57 -05:00
_ = > {
return Err ( anyhow! (
" Unsupported diagnostic code ( \" {} \" ) provided. " ,
code
) )
}
} ;
Ok ( code_action )
} else {
Err ( anyhow! ( " Unsupported diagnostic code provided. " ) )
2021-10-28 19:56:01 -04:00
}
2022-02-04 02:14:57 -05:00
}
/// Given a reference to the code from an LSP diagnostic, determine if the
/// diagnostic is fixable or not
2023-01-24 15:14:49 -05:00
pub fn is_fixable ( diagnostic : & lsp_types ::Diagnostic ) -> bool {
if let Some ( lsp ::NumberOrString ::String ( code ) ) = & diagnostic . code {
2023-01-27 17:36:23 -05:00
matches! (
code . as_str ( ) ,
" import-map-remap "
| " no-cache "
| " no-cache-npm "
2023-09-07 09:09:16 -04:00
| " no-attribute-type "
2023-01-27 17:36:23 -05:00
| " redirect "
| " import-node-prefix-missing "
)
2022-02-04 02:14:57 -05:00
} else {
false
}
}
/// Convert to an lsp Diagnostic when the range the diagnostic applies to is
/// provided.
2022-03-23 09:54:22 -04:00
pub fn to_lsp_diagnostic ( & self , range : & lsp ::Range ) -> lsp ::Diagnostic {
2022-02-04 02:14:57 -05:00
let ( severity , message , data ) = match self {
Self ::DenoWarn ( message ) = > ( lsp ::DiagnosticSeverity ::WARNING , message . to_string ( ) , None ) ,
2023-01-27 10:43:16 -05:00
Self ::ImportMapRemap { from , to } = > ( lsp ::DiagnosticSeverity ::HINT , format! ( " The import specifier can be remapped to \" {to} \" which will resolve it via the active import map. " ) , Some ( json! ( { " from " : from , " to " : to } ) ) ) ,
2023-09-07 09:09:16 -04:00
Self ::InvalidAttributeType ( assert_type ) = > ( lsp ::DiagnosticSeverity ::ERROR , format! ( " The module is a JSON module and expected an attribute type of \" json \" . Instead got \" {assert_type} \" . " ) , None ) ,
Self ::NoAttributeType = > ( lsp ::DiagnosticSeverity ::ERROR , " The module is a JSON module and not being imported with an import attribute. Consider adding `with { type: \" json \" }` to the import statement. " . to_string ( ) , None ) ,
2023-07-10 22:27:22 -04:00
Self ::NoCache ( specifier ) = > ( lsp ::DiagnosticSeverity ::ERROR , format! ( " Uncached or missing remote URL: {specifier} " ) , Some ( json! ( { " specifier " : specifier } ) ) ) ,
2023-08-21 05:53:52 -04:00
Self ::NoCacheNpm ( pkg_req , specifier ) = > ( lsp ::DiagnosticSeverity ::ERROR , format! ( " Uncached or missing npm package: {} " , pkg_req ) , Some ( json! ( { " specifier " : specifier } ) ) ) ,
2023-07-10 22:27:22 -04:00
Self ::NoLocal ( specifier ) = > ( lsp ::DiagnosticSeverity ::ERROR , format! ( " Unable to load a local module: {specifier} \n Please check the file path. " ) , None ) ,
2023-01-27 10:43:16 -05:00
Self ::Redirect { from , to } = > ( lsp ::DiagnosticSeverity ::INFORMATION , format! ( " The import of \" {from} \" was redirected to \" {to} \" . " ) , Some ( json! ( { " specifier " : from , " redirect " : to } ) ) ) ,
2023-01-24 15:14:49 -05:00
Self ::ResolutionError ( err ) = > (
lsp ::DiagnosticSeverity ::ERROR ,
enhanced_resolution_error_message ( err ) ,
2023-01-27 17:36:23 -05:00
graph_util ::get_resolution_error_bare_node_specifier ( err )
. map ( | specifier | json! ( { " specifier " : specifier } ) )
2023-01-24 15:14:49 -05:00
) ,
2023-01-24 09:05:54 -05:00
Self ::InvalidNodeSpecifier ( specifier ) = > ( lsp ::DiagnosticSeverity ::ERROR , format! ( " Unknown Node built-in module: {} " , specifier . path ( ) ) , None ) ,
2023-10-20 00:02:08 -04:00
Self ::BareNodeSpecifier ( specifier ) = > ( lsp ::DiagnosticSeverity ::WARNING , format! ( " \" {} \" is resolved to \" node: {} \" . If you want to use a built-in Node module, add a \" node: \" prefix. " , specifier , specifier ) , Some ( json! ( { " specifier " : specifier } ) ) ) ,
2022-02-04 02:14:57 -05:00
} ;
lsp ::Diagnostic {
range : * range ,
severity : Some ( severity ) ,
code : Some ( lsp ::NumberOrString ::String ( self . code ( ) . to_string ( ) ) ) ,
2023-07-11 17:10:43 -04:00
source : Some ( DiagnosticSource ::Deno . as_lsp_source ( ) . to_string ( ) ) ,
2022-02-04 02:14:57 -05:00
message ,
data ,
.. Default ::default ( )
2021-10-28 19:56:01 -04:00
}
}
}
2023-02-09 22:00:23 -05:00
fn diagnose_resolution (
2022-07-13 21:12:18 -04:00
snapshot : & language_server ::StateSnapshot ,
2023-10-20 00:02:08 -04:00
dependency_key : & str ,
2023-02-09 22:00:23 -05:00
resolution : & Resolution ,
2021-12-15 22:53:17 -05:00
is_dynamic : bool ,
maybe_assert_type : Option < & str > ,
2023-07-25 17:29:29 -04:00
) -> Vec < DenoDiagnostic > {
2023-04-24 19:52:27 -04:00
let mut diagnostics = vec! [ ] ;
2023-02-09 22:00:23 -05:00
match resolution {
Resolution ::Ok ( resolved ) = > {
let specifier = & resolved . specifier ;
2022-06-17 11:41:28 -04:00
// If the module is a remote module and has a `X-Deno-Warning` header, we
2022-02-04 02:14:57 -05:00
// want a warning diagnostic with that message.
2022-07-13 21:12:18 -04:00
if let Some ( metadata ) = snapshot . cache_metadata . get ( specifier ) {
2022-02-01 21:04:26 -05:00
if let Some ( message ) =
metadata . get ( & cache ::MetadataKey ::Warning ) . cloned ( )
{
2023-04-24 19:52:27 -04:00
diagnostics . push ( DenoDiagnostic ::DenoWarn ( message ) ) ;
2021-11-12 11:42:04 -05:00
}
2022-02-01 21:04:26 -05:00
}
2022-07-13 21:12:18 -04:00
if let Some ( doc ) = snapshot . documents . get ( specifier ) {
2022-02-04 02:14:57 -05:00
let doc_specifier = doc . specifier ( ) ;
// If the module was redirected, we want to issue an informational
// diagnostic that indicates this. This then allows us to issue a code
// action to replace the specifier with the final redirected one.
if doc_specifier ! = specifier {
2023-04-24 19:52:27 -04:00
diagnostics . push ( DenoDiagnostic ::Redirect {
from : specifier . clone ( ) ,
to : doc_specifier . clone ( ) ,
} ) ;
2022-02-04 02:14:57 -05:00
}
2021-12-15 22:53:17 -05:00
if doc . media_type ( ) = = MediaType ::Json {
match maybe_assert_type {
// The module has the correct assertion type, no diagnostic
Some ( " json " ) = > ( ) ,
2023-09-07 09:09:16 -04:00
// The dynamic import statement is missing an attribute type, which
2021-12-15 22:53:17 -05:00
// we might not be able to statically detect, therefore we will
// not provide a potentially incorrect diagnostic.
None if is_dynamic = > ( ) ,
// The module has an incorrect assertion type, diagnostic
2023-09-07 09:09:16 -04:00
Some ( assert_type ) = > diagnostics . push (
DenoDiagnostic ::InvalidAttributeType ( assert_type . to_string ( ) ) ,
) ,
// The module is missing an attribute type, diagnostic
None = > diagnostics . push ( DenoDiagnostic ::NoAttributeType ) ,
2021-12-15 22:53:17 -05:00
}
}
2023-02-21 12:03:48 -05:00
} else if let Ok ( pkg_ref ) =
NpmPackageReqReference ::from_specifier ( specifier )
2022-10-21 11:20:18 -04:00
{
2023-10-03 19:05:06 -04:00
if let Some ( npm_resolver ) = snapshot
. npm
. as_ref ( )
. and_then ( | n | n . npm_resolver . as_managed ( ) )
{
2022-10-21 11:20:18 -04:00
// show diagnostics for npm package references that aren't cached
2023-08-21 05:53:52 -04:00
let req = pkg_ref . into_inner ( ) . req ;
2023-10-03 19:05:06 -04:00
if ! npm_resolver . is_pkg_req_folder_cached ( & req ) {
2023-04-24 19:52:27 -04:00
diagnostics
2023-08-21 05:53:52 -04:00
. push ( DenoDiagnostic ::NoCacheNpm ( req , specifier . clone ( ) ) ) ;
2022-10-21 11:20:18 -04:00
}
}
2023-01-24 09:05:54 -05:00
} else if let Some ( module_name ) = specifier . as_str ( ) . strip_prefix ( " node: " )
{
2023-05-28 14:44:41 -04:00
if ! deno_node ::is_builtin_node_module ( module_name ) {
2023-04-24 19:52:27 -04:00
diagnostics
. push ( DenoDiagnostic ::InvalidNodeSpecifier ( specifier . clone ( ) ) ) ;
2023-10-20 00:02:08 -04:00
} else if module_name = = dependency_key {
let mut is_mapped = false ;
if let Some ( import_map ) = & snapshot . maybe_import_map {
if let Resolution ::Ok ( resolved ) = & resolution {
if import_map . resolve ( module_name , & resolved . specifier ) . is_ok ( ) {
is_mapped = true ;
}
}
}
// show diagnostics for bare node specifiers that aren't mapped by import map
if ! is_mapped {
diagnostics
. push ( DenoDiagnostic ::BareNodeSpecifier ( module_name . to_string ( ) ) ) ;
}
2023-10-03 19:05:06 -04:00
} else if let Some ( npm_resolver ) = snapshot
. npm
. as_ref ( )
. and_then ( | n | n . npm_resolver . as_managed ( ) )
{
2023-01-24 09:05:54 -05:00
// check that a @types/node package exists in the resolver
2023-08-21 05:53:52 -04:00
let types_node_req = PackageReq ::from_str ( " @types/node " ) . unwrap ( ) ;
2023-10-03 19:05:06 -04:00
if ! npm_resolver . is_pkg_req_folder_cached ( & types_node_req ) {
2023-04-24 19:52:27 -04:00
diagnostics . push ( DenoDiagnostic ::NoCacheNpm (
2023-08-21 05:53:52 -04:00
types_node_req ,
2023-04-24 19:52:27 -04:00
ModuleSpecifier ::parse ( " npm:@types/node " ) . unwrap ( ) ,
) ) ;
2023-01-24 09:05:54 -05:00
}
}
2021-11-12 11:42:04 -05:00
} else {
2022-02-04 02:14:57 -05:00
// When the document is not available, it means that it cannot be found
// in the cache or locally on the disk, so we want to issue a diagnostic
// about that.
let deno_diagnostic = match specifier . scheme ( ) {
" file " = > DenoDiagnostic ::NoLocal ( specifier . clone ( ) ) ,
_ = > DenoDiagnostic ::NoCache ( specifier . clone ( ) ) ,
2021-10-28 19:56:01 -04:00
} ;
2023-04-24 19:52:27 -04:00
diagnostics . push ( deno_diagnostic ) ;
2021-05-24 22:34:01 -04:00
}
}
2022-02-04 02:14:57 -05:00
// The specifier resolution resulted in an error, so we want to issue a
// diagnostic for that.
2023-04-24 19:52:27 -04:00
Resolution ::Err ( err ) = > {
diagnostics . push ( DenoDiagnostic ::ResolutionError ( * err . clone ( ) ) )
}
2021-10-28 19:56:01 -04:00
_ = > ( ) ,
2021-05-24 22:34:01 -04:00
}
2023-07-25 17:29:29 -04:00
diagnostics
2021-05-24 22:34:01 -04:00
}
2022-07-13 21:12:18 -04:00
/// Generate diagnostics related to a dependency. The dependency is analyzed to
/// determine if it can be remapped to the active import map as well as surface
/// any diagnostics related to the resolved code or type dependency.
fn diagnose_dependency (
diagnostics : & mut Vec < lsp ::Diagnostic > ,
snapshot : & language_server ::StateSnapshot ,
referrer : & ModuleSpecifier ,
dependency_key : & str ,
dependency : & deno_graph ::Dependency ,
) {
2023-09-28 16:43:45 -04:00
if let Some ( npm ) = & snapshot . npm {
if npm . npm_resolver . in_npm_package ( referrer ) {
2022-10-21 11:20:18 -04:00
return ; // ignore, surface typescript errors instead
}
}
2022-07-13 21:12:18 -04:00
if let Some ( import_map ) = & snapshot . maybe_import_map {
2023-02-09 22:00:23 -05:00
if let Resolution ::Ok ( resolved ) = & dependency . maybe_code {
if let Some ( to ) = import_map . lookup ( & resolved . specifier , referrer ) {
2022-07-13 21:12:18 -04:00
if dependency_key ! = to {
diagnostics . push (
DenoDiagnostic ::ImportMapRemap {
from : dependency_key . to_string ( ) ,
to ,
}
2023-02-09 22:00:23 -05:00
. to_lsp_diagnostic ( & documents ::to_lsp_range ( & resolved . range ) ) ,
2022-07-13 21:12:18 -04:00
) ;
}
}
}
}
2023-07-25 17:29:29 -04:00
let import_ranges : Vec < _ > = dependency
. imports
. iter ( )
. map ( | i | documents ::to_lsp_range ( & i . range ) )
. collect ( ) ;
diagnostics . extend (
diagnose_resolution (
snapshot ,
2023-10-20 00:02:08 -04:00
dependency_key ,
2023-07-25 17:29:29 -04:00
if dependency . maybe_code . is_none ( ) {
& dependency . maybe_type
} else {
& dependency . maybe_code
} ,
dependency . is_dynamic ,
2023-09-07 09:09:16 -04:00
dependency . maybe_attribute_type . as_deref ( ) ,
2023-07-25 17:29:29 -04:00
)
. iter ( )
. flat_map ( | diag | {
import_ranges
. iter ( )
. map ( | range | diag . to_lsp_diagnostic ( range ) )
} ) ,
2022-07-13 21:12:18 -04:00
) ;
2023-04-24 19:52:27 -04:00
// TODO(nayeemrmn): This is a crude way of detecting `@deno-types` which has
// a different specifier and therefore needs a separate call to
// `diagnose_resolution()`. It would be much cleaner if that were modelled as
// a separate dependency: https://github.com/denoland/deno_graph/issues/247.
if ! dependency . maybe_type . is_none ( )
& & ! dependency
. imports
. iter ( )
. any ( | i | dependency . maybe_type . includes ( & i . range . start ) . is_some ( ) )
{
let range = match & dependency . maybe_type {
Resolution ::Ok ( resolved ) = > documents ::to_lsp_range ( & resolved . range ) ,
Resolution ::Err ( error ) = > documents ::to_lsp_range ( error . range ( ) ) ,
Resolution ::None = > unreachable! ( ) ,
} ;
2023-07-25 17:29:29 -04:00
diagnostics . extend (
diagnose_resolution (
snapshot ,
2023-10-20 00:02:08 -04:00
dependency_key ,
2023-07-25 17:29:29 -04:00
& dependency . maybe_type ,
dependency . is_dynamic ,
2023-09-07 09:09:16 -04:00
dependency . maybe_attribute_type . as_deref ( ) ,
2023-07-25 17:29:29 -04:00
)
. iter ( )
. map ( | diag | diag . to_lsp_diagnostic ( & range ) ) ,
2023-04-24 19:52:27 -04:00
) ;
}
2022-07-13 21:12:18 -04:00
}
/// Generate diagnostics that come from Deno module resolution logic (like
/// dependencies) or other Deno specific diagnostics, like the ability to use
/// an import map to shorten an URL.
2023-07-11 17:10:43 -04:00
fn generate_deno_diagnostics (
2022-01-29 17:50:15 -05:00
snapshot : & language_server ::StateSnapshot ,
config : & ConfigSnapshot ,
2022-01-24 15:30:01 -05:00
token : CancellationToken ,
) -> DiagnosticVec {
let mut diagnostics_vec = Vec ::new ( ) ;
2021-11-23 20:04:27 -05:00
2023-03-29 16:25:48 -04:00
for document in snapshot
. documents
. documents ( DocumentsFilter ::OpenDiagnosable )
{
2022-01-24 15:30:01 -05:00
if token . is_cancelled ( ) {
break ;
2021-05-11 10:43:00 -04:00
}
2022-01-24 15:30:01 -05:00
let mut diagnostics = Vec ::new ( ) ;
2022-03-20 21:33:37 -04:00
let specifier = document . specifier ( ) ;
if config . specifier_enabled ( specifier ) {
2022-07-13 21:12:18 -04:00
for ( dependency_key , dependency ) in document . dependencies ( ) {
2022-01-29 17:50:15 -05:00
diagnose_dependency (
& mut diagnostics ,
2022-07-13 21:12:18 -04:00
snapshot ,
specifier ,
2022-10-21 11:20:18 -04:00
dependency_key ,
dependency ,
2022-01-29 17:50:15 -05:00
) ;
}
2021-05-11 10:43:00 -04:00
}
2023-07-11 16:36:39 -04:00
diagnostics_vec . push ( DiagnosticRecord {
specifier : specifier . clone ( ) ,
versioned : VersionedDiagnostics {
version : document . maybe_lsp_version ( ) ,
diagnostics ,
} ,
} ) ;
2022-01-24 15:30:01 -05:00
}
2021-05-11 10:43:00 -04:00
2022-01-24 15:30:01 -05:00
diagnostics_vec
2021-05-11 10:43:00 -04:00
}
2021-10-28 19:56:01 -04:00
#[ cfg(test) ]
mod tests {
use super ::* ;
2023-08-01 20:49:09 -04:00
use crate ::cache ::GlobalHttpCache ;
2023-08-08 10:23:02 -04:00
use crate ::cache ::RealDenoCacheEnv ;
2021-10-28 19:56:01 -04:00
use crate ::lsp ::config ::ConfigSnapshot ;
use crate ::lsp ::config ::Settings ;
use crate ::lsp ::config ::WorkspaceSettings ;
2022-07-13 21:12:18 -04:00
use crate ::lsp ::documents ::Documents ;
2021-10-28 19:56:01 -04:00
use crate ::lsp ::documents ::LanguageId ;
use crate ::lsp ::language_server ::StateSnapshot ;
2023-02-09 22:00:23 -05:00
use pretty_assertions ::assert_eq ;
2021-10-28 19:56:01 -04:00
use std ::path ::Path ;
use std ::path ::PathBuf ;
2022-07-13 21:12:18 -04:00
use std ::sync ::Arc ;
2022-04-01 11:15:37 -04:00
use test_util ::TempDir ;
2021-10-28 19:56:01 -04:00
fn mock_state_snapshot (
fixtures : & [ ( & str , & str , i32 , LanguageId ) ] ,
location : & Path ,
2022-07-13 21:12:18 -04:00
maybe_import_map : Option < ( & str , & str ) > ,
2021-10-28 19:56:01 -04:00
) -> StateSnapshot {
2023-08-08 10:23:02 -04:00
let cache = Arc ::new ( GlobalHttpCache ::new (
location . to_path_buf ( ) ,
RealDenoCacheEnv ,
) ) ;
2023-07-08 16:06:45 -04:00
let mut documents = Documents ::new ( cache ) ;
2021-10-28 19:56:01 -04:00
for ( specifier , source , version , language_id ) in fixtures {
let specifier =
resolve_url ( specifier ) . expect ( " failed to create specifier " ) ;
documents . open (
specifier . clone ( ) ,
* version ,
2022-12-20 15:19:35 -05:00
* language_id ,
2022-05-20 16:40:55 -04:00
( * source ) . into ( ) ,
2021-10-28 19:56:01 -04:00
) ;
}
2022-07-13 21:12:18 -04:00
let maybe_import_map = maybe_import_map . map ( | ( base , json_string ) | {
let base_url = ModuleSpecifier ::parse ( base ) . unwrap ( ) ;
let result = import_map ::parse_from_json ( & base_url , json_string ) . unwrap ( ) ;
if ! result . diagnostics . is_empty ( ) {
panic! ( " unexpected import map diagnostics " ) ;
}
Arc ::new ( result . import_map )
} ) ;
2022-01-19 17:10:14 -05:00
StateSnapshot {
documents ,
2022-07-13 21:12:18 -04:00
maybe_import_map ,
2023-07-08 16:06:45 -04:00
assets : Default ::default ( ) ,
2023-08-01 20:49:09 -04:00
cache_metadata : cache ::CacheMetadata ::new ( Arc ::new (
2023-08-08 10:23:02 -04:00
GlobalHttpCache ::new ( location . to_path_buf ( ) , RealDenoCacheEnv ) ,
2023-07-08 16:06:45 -04:00
) ) ,
2023-09-09 14:37:01 -04:00
config : Default ::default ( ) ,
2023-09-28 16:43:45 -04:00
npm : None ,
2022-01-19 17:10:14 -05:00
}
}
fn mock_config ( ) -> ConfigSnapshot {
2023-09-09 10:04:21 -04:00
let root_uri = resolve_url ( " file:/// " ) . unwrap ( ) ;
2022-01-19 17:10:14 -05:00
ConfigSnapshot {
2021-10-28 19:56:01 -04:00
settings : Settings {
2023-10-24 16:27:27 -04:00
unscoped : WorkspaceSettings {
2023-09-01 16:13:13 -04:00
enable : Some ( true ) ,
2021-10-28 19:56:01 -04:00
lint : true ,
.. Default ::default ( )
} ,
.. Default ::default ( )
} ,
2023-09-09 10:04:21 -04:00
workspace_folders : vec ! [ (
root_uri . clone ( ) ,
lsp ::WorkspaceFolder {
uri : root_uri ,
name : " " . to_string ( ) ,
} ,
) ] ,
2021-10-28 19:56:01 -04:00
.. Default ::default ( )
}
}
fn setup (
2022-04-01 11:15:37 -04:00
temp_dir : & TempDir ,
2021-10-28 19:56:01 -04:00
sources : & [ ( & str , & str , i32 , LanguageId ) ] ,
2022-07-13 21:12:18 -04:00
maybe_import_map : Option < ( & str , & str ) > ,
2022-01-29 17:50:15 -05:00
) -> ( StateSnapshot , PathBuf ) {
2023-06-10 11:09:45 -04:00
let location = temp_dir . path ( ) . join ( " deps " ) . to_path_buf ( ) ;
2022-07-13 21:12:18 -04:00
let state_snapshot =
mock_state_snapshot ( sources , & location , maybe_import_map ) ;
2022-01-29 17:50:15 -05:00
( state_snapshot , location )
2021-10-28 19:56:01 -04:00
}
#[ tokio::test ]
2022-01-29 17:50:15 -05:00
async fn test_enabled_then_disabled_specifier ( ) {
2022-04-01 11:15:37 -04:00
let temp_dir = TempDir ::new ( ) ;
2023-07-08 16:06:45 -04:00
let ( snapshot , cache_location ) = setup (
2022-04-01 11:15:37 -04:00
& temp_dir ,
& [ (
" file:///a.ts " ,
r #" import * as b from " . / b . ts " ;
2022-01-29 17:50:15 -05:00
let a : any = " a " ;
let c : number = " a " ;
2021-10-28 19:56:01 -04:00
" #,
2022-04-01 11:15:37 -04:00
1 ,
LanguageId ::TypeScript ,
) ] ,
2022-07-13 21:12:18 -04:00
None ,
2022-04-01 11:15:37 -04:00
) ;
2022-01-29 17:50:15 -05:00
let snapshot = Arc ::new ( snapshot ) ;
2023-08-08 10:23:02 -04:00
let cache =
Arc ::new ( GlobalHttpCache ::new ( cache_location , RealDenoCacheEnv ) ) ;
2023-07-08 16:06:45 -04:00
let ts_server = TsServer ::new ( Default ::default ( ) , cache ) ;
2022-01-29 17:50:15 -05:00
// test enabled
{
let enabled_config = mock_config ( ) ;
let diagnostics = generate_lint_diagnostics (
& snapshot ,
& enabled_config ,
2023-01-07 15:22:09 -05:00
& Default ::default ( ) ,
2022-01-29 17:50:15 -05:00
Default ::default ( ) ,
2023-07-11 17:10:43 -04:00
) ;
2022-01-29 17:50:15 -05:00
assert_eq! ( get_diagnostics_for_single ( diagnostics ) . len ( ) , 6 ) ;
2022-02-02 09:25:22 -05:00
let diagnostics = generate_ts_diagnostics (
snapshot . clone ( ) ,
& enabled_config ,
& ts_server ,
Default ::default ( ) ,
)
. await
. unwrap ( ) ;
2023-03-21 11:46:40 -04:00
assert_eq! ( get_diagnostics_for_single ( diagnostics ) . len ( ) , 5 ) ;
2022-07-13 21:12:18 -04:00
let diagnostics = generate_deno_diagnostics (
2022-01-29 17:50:15 -05:00
& snapshot ,
& enabled_config ,
Default ::default ( ) ,
2023-07-11 17:10:43 -04:00
) ;
2022-01-29 17:50:15 -05:00
assert_eq! ( get_diagnostics_for_single ( diagnostics ) . len ( ) , 1 ) ;
}
// now test disabled specifier
{
let mut disabled_config = mock_config ( ) ;
2023-10-24 16:27:27 -04:00
disabled_config . settings . unscoped = WorkspaceSettings {
enable : Some ( false ) ,
.. Default ::default ( )
} ;
2022-01-29 17:50:15 -05:00
let diagnostics = generate_lint_diagnostics (
& snapshot ,
& disabled_config ,
2023-01-07 15:22:09 -05:00
& Default ::default ( ) ,
2022-01-29 17:50:15 -05:00
Default ::default ( ) ,
2023-07-11 17:10:43 -04:00
) ;
2022-01-29 17:50:15 -05:00
assert_eq! ( get_diagnostics_for_single ( diagnostics ) . len ( ) , 0 ) ;
2022-02-02 09:25:22 -05:00
let diagnostics = generate_ts_diagnostics (
snapshot . clone ( ) ,
& disabled_config ,
& ts_server ,
Default ::default ( ) ,
)
. await
. unwrap ( ) ;
2022-01-29 17:50:15 -05:00
assert_eq! ( get_diagnostics_for_single ( diagnostics ) . len ( ) , 0 ) ;
2022-07-13 21:12:18 -04:00
let diagnostics = generate_deno_diagnostics (
2022-01-29 17:50:15 -05:00
& snapshot ,
& disabled_config ,
Default ::default ( ) ,
2023-07-11 17:10:43 -04:00
) ;
2022-01-29 17:50:15 -05:00
assert_eq! ( get_diagnostics_for_single ( diagnostics ) . len ( ) , 0 ) ;
}
}
fn get_diagnostics_for_single (
diagnostic_vec : DiagnosticVec ,
) -> Vec < lsp ::Diagnostic > {
assert_eq! ( diagnostic_vec . len ( ) , 1 ) ;
2023-07-11 16:36:39 -04:00
diagnostic_vec
. into_iter ( )
. next ( )
. unwrap ( )
. versioned
. diagnostics
2021-10-28 19:56:01 -04:00
}
2022-02-02 09:25:22 -05:00
2022-07-13 21:12:18 -04:00
#[ tokio::test ]
async fn test_deno_diagnostics_with_import_map ( ) {
let temp_dir = TempDir ::new ( ) ;
let ( snapshot , _ ) = setup (
& temp_dir ,
& [
2023-12-01 21:20:06 -05:00
(
" file:///std/assert/mod.ts " ,
" export function assert() {} " ,
1 ,
LanguageId ::TypeScript ,
) ,
(
" file:///a/file.ts " ,
" import { assert } from \" ../std/assert/mod.ts \" ; \n \n assert(); \n " ,
1 ,
LanguageId ::TypeScript ,
) ,
2022-07-13 21:12:18 -04:00
] ,
2023-12-01 21:20:06 -05:00
Some ( (
" file:///a/import-map.json " ,
r #" {
2022-07-13 21:12:18 -04:00
" imports " : {
" /~/std/ " : " ../std/ "
}
2023-12-01 21:20:06 -05:00
} " #,
) ) ,
2022-07-13 21:12:18 -04:00
) ;
let config = mock_config ( ) ;
let token = CancellationToken ::new ( ) ;
2023-07-11 17:10:43 -04:00
let actual = generate_deno_diagnostics ( & snapshot , & config , token ) ;
2022-07-13 21:12:18 -04:00
assert_eq! ( actual . len ( ) , 2 ) ;
2023-07-11 16:36:39 -04:00
for record in actual {
match record . specifier . as_str ( ) {
2023-12-01 21:20:06 -05:00
" file:///std/assert/mod.ts " = > {
2023-07-11 16:36:39 -04:00
assert_eq! ( json! ( record . versioned . diagnostics ) , json! ( [ ] ) )
2022-07-13 21:12:18 -04:00
}
" file:///a/file.ts " = > assert_eq! (
2023-07-11 16:36:39 -04:00
json! ( record . versioned . diagnostics ) ,
2022-07-13 21:12:18 -04:00
json! ( [
{
" range " : {
" start " : {
" line " : 0 ,
" character " : 23
} ,
" end " : {
" line " : 0 ,
2023-12-01 21:20:06 -05:00
" character " : 45
2022-07-13 21:12:18 -04:00
}
} ,
" severity " : 4 ,
" code " : " import-map-remap " ,
" source " : " deno " ,
2023-12-01 21:20:06 -05:00
" message " : " The import specifier can be remapped to \" /~/std/assert/mod.ts \" which will resolve it via the active import map. " ,
2022-07-13 21:12:18 -04:00
" data " : {
2023-12-01 21:20:06 -05:00
" from " : " ../std/assert/mod.ts " ,
" to " : " /~/std/assert/mod.ts "
2022-07-13 21:12:18 -04:00
}
}
] )
) ,
2023-07-11 16:36:39 -04:00
_ = > unreachable! ( " unexpected specifier {} " , record . specifier ) ,
2022-07-13 21:12:18 -04:00
}
}
}
#[ test ]
fn test_get_code_action_import_map_remap ( ) {
let specifier = ModuleSpecifier ::parse ( " file:///a/file.ts " ) . unwrap ( ) ;
let result = DenoDiagnostic ::get_code_action ( & specifier , & lsp ::Diagnostic {
range : lsp ::Range {
start : lsp ::Position { line : 0 , character : 23 } ,
end : lsp ::Position { line : 0 , character : 50 } ,
} ,
severity : Some ( lsp ::DiagnosticSeverity ::HINT ) ,
code : Some ( lsp ::NumberOrString ::String ( " import-map-remap " . to_string ( ) ) ) ,
source : Some ( " deno " . to_string ( ) ) ,
2023-12-01 21:20:06 -05:00
message : " The import specifier can be remapped to \" /~/std/assert/mod.ts \" which will resolve it via the active import map. " . to_string ( ) ,
2022-07-13 21:12:18 -04:00
data : Some ( json! ( {
2023-12-01 21:20:06 -05:00
" from " : " ../std/assert/mod.ts " ,
" to " : " /~/std/assert/mod.ts "
2022-07-13 21:12:18 -04:00
} ) ) ,
.. Default ::default ( )
} ) ;
assert! ( result . is_ok ( ) ) ;
let actual = result . unwrap ( ) ;
assert_eq! (
json! ( actual ) ,
json! ( {
2023-12-01 21:20:06 -05:00
" title " : " Update \" ../std/assert/mod.ts \" to \" /~/std/assert/mod.ts \" to use import map. " ,
2022-07-13 21:12:18 -04:00
" kind " : " quickfix " ,
" diagnostics " : [
{
" range " : {
" start " : {
" line " : 0 ,
" character " : 23
} ,
" end " : {
" line " : 0 ,
" character " : 50
}
} ,
" severity " : 4 ,
" code " : " import-map-remap " ,
" source " : " deno " ,
2023-12-01 21:20:06 -05:00
" message " : " The import specifier can be remapped to \" /~/std/assert/mod.ts \" which will resolve it via the active import map. " ,
2022-07-13 21:12:18 -04:00
" data " : {
2023-12-01 21:20:06 -05:00
" from " : " ../std/assert/mod.ts " ,
" to " : " /~/std/assert/mod.ts "
2022-07-13 21:12:18 -04:00
}
}
] ,
" edit " : {
" changes " : {
" file:///a/file.ts " : [
{
" range " : {
" start " : {
" line " : 0 ,
" character " : 23
} ,
" end " : {
" line " : 0 ,
" character " : 50
}
} ,
2023-12-01 21:20:06 -05:00
" newText " : " \" /~/std/assert/mod.ts \" "
2022-07-13 21:12:18 -04:00
}
]
}
}
} )
) ;
}
2023-04-24 19:52:27 -04:00
#[ tokio::test ]
async fn duplicate_diagnostics_for_duplicate_imports ( ) {
let temp_dir = TempDir ::new ( ) ;
let ( snapshot , _ ) = setup (
& temp_dir ,
& [ (
" file:///a.ts " ,
r #"
// @deno-types="bad.d.ts"
import " bad.js " ;
import " bad.js " ;
" #,
1 ,
LanguageId ::TypeScript ,
) ] ,
None ,
) ;
let config = mock_config ( ) ;
let token = CancellationToken ::new ( ) ;
2023-07-11 17:10:43 -04:00
let actual = generate_deno_diagnostics ( & snapshot , & config , token ) ;
2023-04-24 19:52:27 -04:00
assert_eq! ( actual . len ( ) , 1 ) ;
2023-07-11 16:36:39 -04:00
let record = actual . first ( ) . unwrap ( ) ;
2023-04-24 19:52:27 -04:00
assert_eq! (
2023-07-11 16:36:39 -04:00
json! ( record . versioned . diagnostics ) ,
2023-04-24 19:52:27 -04:00
json! ( [
{
" range " : {
" start " : {
" line " : 2 ,
" character " : 15
} ,
" end " : {
" line " : 2 ,
" character " : 23
}
} ,
" severity " : 1 ,
" code " : " import-prefix-missing " ,
" source " : " deno " ,
" message " : " Relative import path \" bad.js \" not prefixed with / or ./ or ../ " ,
} ,
{
" range " : {
" start " : {
" line " : 3 ,
" character " : 15
} ,
" end " : {
" line " : 3 ,
" character " : 23
}
} ,
" severity " : 1 ,
" code " : " import-prefix-missing " ,
" source " : " deno " ,
" message " : " Relative import path \" bad.js \" not prefixed with / or ./ or ../ " ,
} ,
{
" range " : {
" start " : {
" line " : 1 ,
" character " : 23
} ,
" end " : {
" line " : 1 ,
" character " : 33
}
} ,
" severity " : 1 ,
" code " : " import-prefix-missing " ,
" source " : " deno " ,
" message " : " Relative import path \" bad.d.ts \" not prefixed with / or ./ or ../ " ,
} ,
] )
) ;
}
2021-10-28 19:56:01 -04:00
}