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-05-11 10:43:00 -04:00
use super ::analysis ;
2021-10-28 19:56:01 -04:00
use super ::documents ;
2021-11-22 18:10:33 -05:00
use super ::documents ::Document ;
2021-10-28 19:56:01 -04:00
use super ::documents ::Documents ;
2021-03-09 21:41:35 -05:00
use super ::language_server ;
2020-12-07 05:46:39 -05:00
use super ::tsc ;
use crate ::diagnostics ;
2021-11-23 10:38:11 -05:00
use crate ::fs_util ::specifier_to_file_path ;
2020-12-07 05:46:39 -05:00
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 ;
2021-05-11 10:43:00 -04:00
use deno_core ::resolve_url ;
2021-05-19 08:28:23 -04:00
use deno_core ::serde_json ::json ;
2021-01-22 05:03:16 -05:00
use deno_core ::ModuleSpecifier ;
2021-10-21 07:05:43 -04:00
use deno_runtime ::tokio_util ::create_basic_runtime ;
2021-03-26 12:34:25 -04:00
use log ::error ;
2021-01-29 14:34:33 -05:00
use lspower ::lsp ;
2020-12-07 05:46:39 -05:00
use std ::collections ::HashMap ;
use std ::collections ::HashSet ;
use std ::mem ;
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 ::sleep ;
use tokio ::time ::Duration ;
use tokio ::time ::Instant ;
2020-12-07 05:46:39 -05:00
2021-05-11 10:43:00 -04:00
pub type DiagnosticRecord =
( ModuleSpecifier , Option < i32 > , Vec < lsp ::Diagnostic > ) ;
pub type DiagnosticVec = Vec < DiagnosticRecord > ;
type TsDiagnosticsMap = HashMap < String , Vec < diagnostics ::Diagnostic > > ;
#[ derive(Debug, Hash, Clone, PartialEq, Eq) ]
pub ( crate ) enum DiagnosticSource {
2020-12-24 05:53:03 -05:00
Deno ,
2021-05-11 10:43:00 -04:00
DenoLint ,
2020-12-07 05:46:39 -05:00
TypeScript ,
}
2021-05-11 10:43:00 -04:00
#[ derive(Debug, Default) ]
struct DiagnosticCollection {
map : HashMap < ( ModuleSpecifier , DiagnosticSource ) , Vec < lsp ::Diagnostic > > ,
versions : HashMap < ModuleSpecifier , HashMap < DiagnosticSource , i32 > > ,
changes : HashSet < ModuleSpecifier > ,
2021-03-09 21:41:35 -05:00
}
2021-05-11 10:43:00 -04:00
impl DiagnosticCollection {
pub fn get (
& self ,
specifier : & ModuleSpecifier ,
source : DiagnosticSource ,
) -> impl Iterator < Item = & lsp ::Diagnostic > {
self
. map
. get ( & ( specifier . clone ( ) , source ) )
. into_iter ( )
. flatten ( )
2021-03-09 21:41:35 -05:00
}
2021-05-11 10:43:00 -04:00
pub fn get_version (
& self ,
specifier : & ModuleSpecifier ,
source : & DiagnosticSource ,
) -> Option < i32 > {
let source_version = self . versions . get ( specifier ) ? ;
source_version . get ( source ) . cloned ( )
}
2021-03-09 21:41:35 -05:00
2021-05-11 10:43:00 -04:00
pub fn set ( & mut self , source : DiagnosticSource , record : DiagnosticRecord ) {
let ( specifier , maybe_version , diagnostics ) = record ;
self
. map
. insert ( ( specifier . clone ( ) , source . clone ( ) ) , diagnostics ) ;
if let Some ( version ) = maybe_version {
let source_version = self . versions . entry ( specifier . clone ( ) ) . or_default ( ) ;
source_version . insert ( source , version ) ;
2021-05-09 21:16:04 -04:00
}
2021-05-11 10:43:00 -04:00
self . changes . insert ( specifier ) ;
}
2021-03-09 21:41:35 -05:00
2021-05-11 10:43:00 -04:00
pub fn take_changes ( & mut self ) -> Option < HashSet < ModuleSpecifier > > {
if self . changes . is_empty ( ) {
None
} else {
Some ( mem ::take ( & mut self . changes ) )
2021-05-09 21:16:04 -04:00
}
2021-05-11 10:43:00 -04:00
}
2021-03-09 21:41:35 -05:00
}
2021-10-28 19:56:01 -04:00
#[ derive(Debug, Default) ]
2021-05-11 10:43:00 -04:00
pub ( crate ) struct DiagnosticsServer {
channel : Option < mpsc ::UnboundedSender < ( ) > > ,
collection : Arc < Mutex < DiagnosticCollection > > ,
}
2021-03-09 21:41:35 -05:00
impl DiagnosticsServer {
2021-05-11 10:43:00 -04:00
pub ( crate ) async fn get (
& self ,
specifier : & ModuleSpecifier ,
source : DiagnosticSource ,
) -> Vec < lsp ::Diagnostic > {
self
. collection
. lock ( )
. await
. get ( specifier , source )
. cloned ( )
. collect ( )
}
2021-06-03 07:13:53 -04:00
pub ( crate ) async fn invalidate ( & self , specifiers : Vec < ModuleSpecifier > ) {
let mut collection = self . collection . lock ( ) . await ;
2021-07-25 01:33:42 -04:00
for specifier in & specifiers {
collection . versions . remove ( specifier ) ;
2021-06-03 07:13:53 -04:00
}
2021-03-09 21:41:35 -05:00
}
2021-07-25 01:33:42 -04:00
pub ( crate ) async fn invalidate_all ( & self ) {
let mut collection = self . collection . lock ( ) . await ;
collection . versions . clear ( ) ;
}
2021-03-09 21:41:35 -05:00
pub ( crate ) fn start (
& mut self ,
2021-05-11 10:43:00 -04:00
language_server : Arc < Mutex < language_server ::Inner > > ,
client : lspower ::Client ,
2021-03-09 21:41:35 -05:00
ts_server : Arc < tsc ::TsServer > ,
) {
2021-05-11 10:43:00 -04:00
let ( tx , mut rx ) = mpsc ::unbounded_channel ::< ( ) > ( ) ;
self . channel = Some ( tx ) ;
let collection = self . collection . clone ( ) ;
2021-03-09 21:41:35 -05:00
let _join_handle = thread ::spawn ( move | | {
let runtime = create_basic_runtime ( ) ;
runtime . block_on ( async {
2021-03-18 16:26:41 -04:00
// 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 ) ;
// If the debounce timer isn't active, it will be set to expire "never",
// which is actually just 1 year in the future.
const NEVER : Duration = Duration ::from_secs ( 365 * 24 * 60 * 60 ) ;
2021-03-09 21:41:35 -05:00
2021-03-18 16:26:41 -04:00
// A flag that is set whenever something has changed that requires the
// diagnostics collection to be updated.
let mut dirty = false ;
2021-03-09 21:41:35 -05:00
2021-03-18 16:26:41 -04:00
let debounce_timer = sleep ( NEVER ) ;
tokio ::pin! ( debounce_timer ) ;
2021-03-09 21:41:35 -05:00
2021-03-18 16:26:41 -04:00
loop {
2021-03-09 21:41:35 -05:00
// "race" the next message off the rx queue or the debounce timer.
2021-03-18 16:26:41 -04:00
// The debounce timer gets reset every time a message comes off the
// queue. When the debounce timer expires, a snapshot of the most
// up-to-date state is used to produce diagnostics.
2021-03-09 21:41:35 -05:00
tokio ::select! {
2021-03-18 16:26:41 -04:00
maybe_request = rx . recv ( ) = > {
match maybe_request {
2021-05-11 10:43:00 -04:00
// channel has closed
None = > break ,
Some ( _ ) = > {
2021-03-18 16:26:41 -04:00
dirty = true ;
debounce_timer . as_mut ( ) . reset ( Instant ::now ( ) + DELAY ) ;
}
2021-03-09 21:41:35 -05:00
}
}
2021-03-18 16:26:41 -04:00
_ = debounce_timer . as_mut ( ) , if dirty = > {
dirty = false ;
debounce_timer . as_mut ( ) . reset ( Instant ::now ( ) + NEVER ) ;
2021-05-20 05:56:48 -04:00
let snapshot = language_server . lock ( ) . await . snapshot ( ) . unwrap ( ) ;
2021-03-18 16:26:41 -04:00
update_diagnostics (
& client ,
2021-05-07 07:05:32 -04:00
collection . clone ( ) ,
2021-11-18 13:50:24 -05:00
snapshot ,
2021-03-18 16:26:41 -04:00
& ts_server
) . await ;
2021-03-09 21:41:35 -05:00
}
}
}
} )
} ) ;
}
2021-05-11 10:43:00 -04:00
pub ( crate ) fn update ( & self ) -> Result < ( ) , AnyError > {
if let Some ( tx ) = & self . channel {
tx . send ( ( ) ) . 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
}
2021-01-29 14:34:33 -05:00
impl < ' a > From < & ' a diagnostics ::DiagnosticCategory > for lsp ::DiagnosticSeverity {
2020-12-21 08:44:26 -05:00
fn from ( category : & ' a diagnostics ::DiagnosticCategory ) -> Self {
match category {
2021-01-29 14:34:33 -05:00
diagnostics ::DiagnosticCategory ::Error = > lsp ::DiagnosticSeverity ::Error ,
2020-12-21 08:44:26 -05:00
diagnostics ::DiagnosticCategory ::Warning = > {
2021-01-29 14:34:33 -05:00
lsp ::DiagnosticSeverity ::Warning
2020-12-21 08:44:26 -05:00
}
diagnostics ::DiagnosticCategory ::Suggestion = > {
2021-01-29 14:34:33 -05:00
lsp ::DiagnosticSeverity ::Hint
2020-12-21 08:44:26 -05:00
}
diagnostics ::DiagnosticCategory ::Message = > {
2021-01-29 14:34:33 -05:00
lsp ::DiagnosticSeverity ::Information
2020-12-21 08:44:26 -05:00
}
}
}
}
2021-01-29 14:34:33 -05:00
impl < ' a > From < & ' a diagnostics ::Position > for lsp ::Position {
2020-12-21 08:44:26 -05:00
fn from ( pos : & ' a diagnostics ::Position ) -> Self {
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
fn get_diagnostic_message ( diagnostic : & diagnostics ::Diagnostic ) -> String {
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 (
start : & diagnostics ::Position ,
end : & diagnostics ::Position ,
) -> lsp ::Range {
lsp ::Range {
start : start . into ( ) ,
end : end . into ( ) ,
}
}
2020-12-07 05:46:39 -05:00
fn to_lsp_related_information (
related_information : & Option < Vec < diagnostics ::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 | {
if let ( Some ( source ) , Some ( start ) , Some ( end ) ) =
( & ri . source , & ri . start , & ri . end )
{
2021-07-30 09:03:41 -04:00
let uri = lsp ::Url ::parse ( source ) . 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 (
2021-05-11 10:43:00 -04:00
diagnostics : Vec < diagnostics ::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 ,
source : Some ( " deno-ts " . to_string ( ) ) ,
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
| 7028 = > Some ( vec! [ lsp ::DiagnosticTag ::Unnecessary ] ) ,
// These are codes that indicated the variable is deprecated.
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
}
2021-11-22 18:10:33 -05:00
// Filters documents according to the `include` and the `exclude` lists (from `StateSnapshot::maybe_lint_config`).
// If a document is in the `exclude` list - then it be removed.
// If the `include` list is not empty, and a document is not in - then it be removed too.
2021-11-23 10:38:11 -05:00
fn filter_lint_documents (
2021-11-22 18:10:33 -05:00
snapshot : & language_server ::StateSnapshot ,
documents : & mut Vec < Document > ,
) {
2021-11-23 10:38:11 -05:00
let lint_config = match & snapshot . maybe_lint_config {
2021-11-22 18:10:33 -05:00
Some ( config ) = > config ,
None = > return ,
} ;
documents . retain ( | doc | {
2021-11-23 10:38:11 -05:00
let path = if let Ok ( file_path ) = specifier_to_file_path ( doc . specifier ( ) ) {
file_path
} else {
return false ;
} ;
2021-11-22 18:10:33 -05:00
// Skip files which is in the exclude list.
2021-11-23 10:38:11 -05:00
if lint_config
. files
. exclude
. iter ( )
. any ( | i | path . starts_with ( i ) )
{
2021-11-22 18:10:33 -05:00
return false ;
}
// Early return if the include list is empty.
2021-11-23 10:38:11 -05:00
if lint_config . files . include . is_empty ( ) {
2021-11-22 18:10:33 -05:00
return true ;
}
2021-11-23 10:38:11 -05:00
// Ignore files not in the include list.
lint_config
. files
. include
2021-11-22 18:10:33 -05:00
. iter ( )
2021-11-23 10:38:11 -05:00
. any ( | i | path . starts_with ( i ) )
2021-11-22 18:10:33 -05:00
} ) ;
}
2021-05-11 10:43:00 -04:00
async fn generate_lint_diagnostics (
snapshot : & language_server ::StateSnapshot ,
2021-05-07 07:05:32 -04:00
collection : Arc < Mutex < DiagnosticCollection > > ,
2020-12-07 05:46:39 -05:00
) -> Result < DiagnosticVec , AnyError > {
2021-11-22 18:10:33 -05:00
let mut documents = snapshot . documents . documents ( true , true ) ;
2021-05-20 05:56:48 -04:00
let workspace_settings = snapshot . config . settings . workspace . clone ( ) ;
2021-10-11 18:02:33 -04:00
let maybe_lint_config = snapshot . maybe_lint_config . clone ( ) ;
2021-11-22 18:10:33 -05:00
2021-11-23 10:38:11 -05:00
filter_lint_documents ( snapshot , & mut documents ) ;
2021-11-22 18:10:33 -05:00
2021-05-11 10:43:00 -04:00
tokio ::task ::spawn ( async move {
let mut diagnostics_vec = Vec ::new ( ) ;
if workspace_settings . lint {
2021-11-12 11:42:04 -05:00
for document in documents {
let version = document . maybe_lsp_version ( ) ;
2021-05-11 10:43:00 -04:00
let current_version = collection
. lock ( )
. await
2021-11-12 11:42:04 -05:00
. get_version ( document . specifier ( ) , & DiagnosticSource ::DenoLint ) ;
2021-06-02 06:29:58 -04:00
if version ! = current_version {
2021-11-12 11:42:04 -05:00
let diagnostics = match document . maybe_parsed_source ( ) {
2021-10-28 19:56:01 -04:00
Some ( Ok ( parsed_source ) ) = > {
2021-10-11 18:02:33 -04:00
if let Ok ( references ) = analysis ::get_lint_references (
2021-10-28 19:56:01 -04:00
& parsed_source ,
2021-10-11 18:02:33 -04:00
maybe_lint_config . as_ref ( ) ,
) {
2021-09-07 10:39:32 -04:00
references
. into_iter ( )
. map ( | r | r . to_diagnostic ( ) )
. collect ::< Vec < _ > > ( )
} else {
Vec ::new ( )
}
2021-05-11 10:43:00 -04:00
}
2021-09-07 10:39:32 -04:00
Some ( Err ( _ ) ) = > Vec ::new ( ) ,
None = > {
2021-11-12 11:42:04 -05:00
error! ( " Missing file contents for: {} " , document . specifier ( ) ) ;
2021-09-07 10:39:32 -04:00
Vec ::new ( )
}
} ;
2021-11-12 11:42:04 -05:00
diagnostics_vec . push ( (
document . specifier ( ) . clone ( ) ,
version ,
diagnostics ,
) ) ;
2021-05-09 21:16:04 -04:00
}
2021-05-07 07:05:32 -04:00
}
2021-01-22 05:03:16 -05:00
}
2021-05-11 10:43:00 -04:00
Ok ( diagnostics_vec )
} )
. await
. unwrap ( )
}
async fn generate_ts_diagnostics (
2021-11-18 13:50:24 -05:00
snapshot : Arc < language_server ::StateSnapshot > ,
2021-05-11 10:43:00 -04:00
collection : Arc < Mutex < DiagnosticCollection > > ,
ts_server : & tsc ::TsServer ,
) -> Result < DiagnosticVec , AnyError > {
let mut diagnostics_vec = Vec ::new ( ) ;
let specifiers : Vec < ModuleSpecifier > = {
let collection = collection . lock ( ) . await ;
snapshot
. documents
2021-11-12 11:42:04 -05:00
. documents ( true , true )
2021-05-11 10:43:00 -04:00
. iter ( )
2021-11-12 11:42:04 -05:00
. filter_map ( | d | {
let version = d . maybe_lsp_version ( ) ;
2021-10-28 19:56:01 -04:00
let current_version =
2021-11-12 11:42:04 -05:00
collection . get_version ( d . specifier ( ) , & DiagnosticSource ::TypeScript ) ;
2021-10-28 19:56:01 -04:00
if version ! = current_version {
2021-11-12 11:42:04 -05:00
Some ( d . specifier ( ) . clone ( ) )
2021-05-18 02:35:46 -04:00
} else {
None
2021-05-11 10:43:00 -04:00
}
} )
. collect ( )
} ;
2021-01-22 05:03:16 -05:00
if ! specifiers . is_empty ( ) {
let req = tsc ::RequestMethod ::GetDiagnostics ( specifiers ) ;
2021-05-11 10:43:00 -04:00
let ts_diagnostics_map : TsDiagnosticsMap =
ts_server . request ( snapshot . clone ( ) , req ) . await ? ;
for ( specifier_str , ts_diagnostics ) in ts_diagnostics_map {
let specifier = resolve_url ( & specifier_str ) ? ;
2021-11-12 11:42:04 -05:00
let version = snapshot
. documents
. get ( & specifier )
. map ( | d | d . maybe_lsp_version ( ) )
. flatten ( ) ;
2021-05-11 10:43:00 -04:00
diagnostics_vec . push ( (
2021-01-22 05:03:16 -05:00
specifier ,
version ,
ts_json_to_diagnostics ( ts_diagnostics ) ,
) ) ;
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
2021-10-28 19:56:01 -04:00
fn resolution_error_as_code (
err : & deno_graph ::ResolutionError ,
) -> lsp ::NumberOrString {
use deno_graph ::ResolutionError ;
use deno_graph ::SpecifierError ;
match err {
ResolutionError ::InvalidDowngrade ( _ , _ ) = > {
lsp ::NumberOrString ::String ( " invalid-downgrade " . to_string ( ) )
}
ResolutionError ::InvalidLocalImport ( _ , _ ) = > {
lsp ::NumberOrString ::String ( " invalid-local-import " . to_string ( ) )
}
ResolutionError ::InvalidSpecifier ( err , _ ) = > match err {
SpecifierError ::ImportPrefixMissing ( _ , _ ) = > {
lsp ::NumberOrString ::String ( " import-prefix-missing " . to_string ( ) )
}
SpecifierError ::InvalidUrl ( _ ) = > {
lsp ::NumberOrString ::String ( " invalid-url " . to_string ( ) )
}
} ,
ResolutionError ::ResolverError ( _ , _ , _ ) = > {
lsp ::NumberOrString ::String ( " resolver-error " . to_string ( ) )
}
}
}
2021-05-24 22:34:01 -04:00
fn diagnose_dependency (
diagnostics : & mut Vec < lsp ::Diagnostic > ,
2021-10-28 19:56:01 -04:00
documents : & Documents ,
resolved : & deno_graph ::Resolved ,
2021-05-24 22:34:01 -04:00
) {
2021-10-28 19:56:01 -04:00
match resolved {
Some ( Ok ( ( specifier , range ) ) ) = > {
2021-11-12 11:42:04 -05:00
if let Some ( doc ) = documents . get ( specifier ) {
if let Some ( message ) = doc . maybe_warning ( ) {
diagnostics . push ( lsp ::Diagnostic {
range : documents ::to_lsp_range ( range ) ,
severity : Some ( lsp ::DiagnosticSeverity ::Warning ) ,
code : Some ( lsp ::NumberOrString ::String ( " deno-warn " . to_string ( ) ) ) ,
source : Some ( " deno " . to_string ( ) ) ,
message ,
.. Default ::default ( )
} )
}
} else {
2021-10-28 19:56:01 -04:00
let ( code , message ) = match specifier . scheme ( ) {
" file " = > ( Some ( lsp ::NumberOrString ::String ( " no-local " . to_string ( ) ) ) , format! ( " Unable to load a local module: \" {} \" . \n Please check the file path. " , specifier ) ) ,
" data " = > ( Some ( lsp ::NumberOrString ::String ( " no-cache-data " . to_string ( ) ) ) , " Uncached data URL. " . to_string ( ) ) ,
" blob " = > ( Some ( lsp ::NumberOrString ::String ( " no-cache-blob " . to_string ( ) ) ) , " Uncached blob URL. " . to_string ( ) ) ,
_ = > ( Some ( lsp ::NumberOrString ::String ( " no-cache " . to_string ( ) ) ) , format! ( " Uncached or missing remote URL: \" {} \" . " , specifier ) ) ,
} ;
2021-05-24 22:34:01 -04:00
diagnostics . push ( lsp ::Diagnostic {
2021-10-28 19:56:01 -04:00
range : documents ::to_lsp_range ( range ) ,
2021-05-24 22:34:01 -04:00
severity : Some ( lsp ::DiagnosticSeverity ::Error ) ,
2021-10-28 19:56:01 -04:00
code ,
2021-05-24 22:34:01 -04:00
source : Some ( " deno " . to_string ( ) ) ,
2021-10-28 19:56:01 -04:00
message ,
data : Some ( json! ( { " specifier " : specifier } ) ) ,
.. Default ::default ( )
} ) ;
2021-05-24 22:34:01 -04:00
}
}
2021-10-28 19:56:01 -04:00
Some ( Err ( err ) ) = > diagnostics . push ( lsp ::Diagnostic {
range : documents ::to_lsp_range ( err . range ( ) ) ,
severity : Some ( lsp ::DiagnosticSeverity ::Error ) ,
code : Some ( resolution_error_as_code ( err ) ) ,
source : Some ( " deno " . to_string ( ) ) ,
message : err . to_string ( ) ,
.. Default ::default ( )
} ) ,
_ = > ( ) ,
2021-05-24 22:34:01 -04:00
}
}
2021-05-11 10:43:00 -04:00
/// Generate diagnostics for dependencies of a module, attempting to resolve
/// dependencies on the local file system or in the DENO_DIR cache.
async fn generate_deps_diagnostics (
2021-11-18 13:50:24 -05:00
snapshot : Arc < language_server ::StateSnapshot > ,
2021-05-07 07:05:32 -04:00
collection : Arc < Mutex < DiagnosticCollection > > ,
2020-12-24 05:53:03 -05:00
) -> Result < DiagnosticVec , AnyError > {
2021-05-11 10:43:00 -04:00
tokio ::task ::spawn ( async move {
let mut diagnostics_vec = Vec ::new ( ) ;
2021-11-18 13:50:24 -05:00
for document in snapshot . documents . documents ( true , true ) {
if ! snapshot . config . specifier_enabled ( document . specifier ( ) ) {
2021-05-09 21:16:04 -04:00
continue ;
}
2021-11-12 11:42:04 -05:00
let version = document . maybe_lsp_version ( ) ;
2021-05-11 10:43:00 -04:00
let current_version = collection
. lock ( )
. await
2021-11-12 11:42:04 -05:00
. get_version ( document . specifier ( ) , & DiagnosticSource ::Deno ) ;
2020-12-24 05:53:03 -05:00
if version ! = current_version {
2021-05-11 10:43:00 -04:00
let mut diagnostics = Vec ::new ( ) ;
2021-11-16 17:23:25 -05:00
for ( _ , dependency ) in document . dependencies ( ) {
diagnose_dependency (
& mut diagnostics ,
2021-11-18 13:50:24 -05:00
& snapshot . documents ,
2021-11-16 17:23:25 -05:00
& dependency . maybe_code ,
) ;
diagnose_dependency (
& mut diagnostics ,
2021-11-18 13:50:24 -05:00
& snapshot . documents ,
2021-11-16 17:23:25 -05:00
& dependency . maybe_type ,
) ;
2020-12-24 05:53:03 -05:00
}
2021-11-12 11:42:04 -05:00
diagnostics_vec . push ( (
document . specifier ( ) . clone ( ) ,
version ,
diagnostics ,
) ) ;
2020-12-24 05:53:03 -05:00
}
}
2021-05-11 10:43:00 -04:00
Ok ( diagnostics_vec )
2020-12-24 05:53:03 -05:00
} )
. await
. unwrap ( )
}
2021-05-11 10:43:00 -04:00
/// Publishes diagnostics to the client.
async fn publish_diagnostics (
client : & lspower ::Client ,
collection : Arc < Mutex < DiagnosticCollection > > ,
snapshot : & language_server ::StateSnapshot ,
) {
let mut collection = collection . lock ( ) . await ;
if let Some ( changes ) = collection . take_changes ( ) {
for specifier in changes {
let mut diagnostics : Vec < lsp ::Diagnostic > =
2021-05-20 05:56:48 -04:00
if snapshot . config . settings . workspace . lint {
2021-05-11 10:43:00 -04:00
collection
. get ( & specifier , DiagnosticSource ::DenoLint )
. cloned ( )
. collect ( )
} else {
Vec ::new ( )
} ;
if snapshot . config . specifier_enabled ( & specifier ) {
diagnostics . extend (
collection
. get ( & specifier , DiagnosticSource ::TypeScript )
. cloned ( ) ,
) ;
diagnostics
. extend ( collection . get ( & specifier , DiagnosticSource ::Deno ) . cloned ( ) ) ;
}
2021-11-12 11:42:04 -05:00
let version = snapshot
. documents
. get ( & specifier )
. map ( | d | d . maybe_lsp_version ( ) )
. flatten ( ) ;
client
. publish_diagnostics ( specifier . clone ( ) , diagnostics , version )
. await ;
2021-05-11 10:43:00 -04:00
}
}
}
/// Updates diagnostics for any specifiers that don't have the correct version
/// generated and publishes the diagnostics to the client.
async fn update_diagnostics (
client : & lspower ::Client ,
collection : Arc < Mutex < DiagnosticCollection > > ,
2021-11-18 13:50:24 -05:00
snapshot : Arc < language_server ::StateSnapshot > ,
2021-05-11 10:43:00 -04:00
ts_server : & tsc ::TsServer ,
) {
let mark = snapshot . performance . mark ( " update_diagnostics " , None ::< ( ) > ) ;
let lint = async {
let mark = snapshot
. performance
. mark ( " update_diagnostics_lint " , None ::< ( ) > ) ;
let collection = collection . clone ( ) ;
2021-11-18 13:50:24 -05:00
let diagnostics = generate_lint_diagnostics ( & snapshot , collection . clone ( ) )
2021-05-11 10:43:00 -04:00
. await
. map_err ( | err | {
error! ( " Error generating lint diagnostics: {} " , err ) ;
} )
. unwrap_or_default ( ) ;
{
let mut collection = collection . lock ( ) . await ;
for diagnostic_record in diagnostics {
collection . set ( DiagnosticSource ::DenoLint , diagnostic_record ) ;
}
}
2021-11-18 13:50:24 -05:00
publish_diagnostics ( client , collection , & snapshot ) . await ;
2021-05-11 10:43:00 -04:00
snapshot . performance . measure ( mark ) ;
} ;
let ts = async {
let mark = snapshot
. performance
. mark ( " update_diagnostics_ts " , None ::< ( ) > ) ;
let collection = collection . clone ( ) ;
let diagnostics =
2021-11-18 13:50:24 -05:00
generate_ts_diagnostics ( snapshot . clone ( ) , collection . clone ( ) , ts_server )
2021-05-11 10:43:00 -04:00
. await
. map_err ( | err | {
error! ( " Error generating TypeScript diagnostics: {} " , err ) ;
} )
. unwrap_or_default ( ) ;
{
let mut collection = collection . lock ( ) . await ;
for diagnostic_record in diagnostics {
collection . set ( DiagnosticSource ::TypeScript , diagnostic_record ) ;
}
}
2021-11-18 13:50:24 -05:00
publish_diagnostics ( client , collection , & snapshot ) . await ;
2021-05-11 10:43:00 -04:00
snapshot . performance . measure ( mark ) ;
} ;
let deps = async {
let mark = snapshot
. performance
. mark ( " update_diagnostics_deps " , None ::< ( ) > ) ;
let collection = collection . clone ( ) ;
2021-11-18 13:50:24 -05:00
let diagnostics =
generate_deps_diagnostics ( snapshot . clone ( ) , collection . clone ( ) )
. await
. map_err ( | err | {
error! ( " Error generating Deno diagnostics: {} " , err ) ;
} )
. unwrap_or_default ( ) ;
2021-05-11 10:43:00 -04:00
{
let mut collection = collection . lock ( ) . await ;
for diagnostic_record in diagnostics {
collection . set ( DiagnosticSource ::Deno , diagnostic_record ) ;
}
}
2021-11-18 13:50:24 -05:00
publish_diagnostics ( client , collection , & snapshot ) . await ;
2021-05-11 10:43:00 -04:00
snapshot . performance . measure ( mark ) ;
} ;
tokio ::join! ( lint , ts , deps ) ;
snapshot . performance . measure ( mark ) ;
}
2021-10-28 19:56:01 -04:00
#[ cfg(test) ]
mod tests {
use super ::* ;
use crate ::lsp ::config ::ConfigSnapshot ;
use crate ::lsp ::config ::Settings ;
use crate ::lsp ::config ::WorkspaceSettings ;
use crate ::lsp ::documents ::LanguageId ;
use crate ::lsp ::language_server ::StateSnapshot ;
use std ::path ::Path ;
use std ::path ::PathBuf ;
use tempfile ::TempDir ;
fn mock_state_snapshot (
fixtures : & [ ( & str , & str , i32 , LanguageId ) ] ,
location : & Path ,
) -> StateSnapshot {
2021-11-18 13:50:24 -05:00
let mut documents = Documents ::new ( location ) ;
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 ,
language_id . clone ( ) ,
Arc ::new ( source . to_string ( ) ) ,
) ;
}
let config = ConfigSnapshot {
settings : Settings {
workspace : WorkspaceSettings {
enable : true ,
lint : true ,
.. Default ::default ( )
} ,
.. Default ::default ( )
} ,
.. Default ::default ( )
} ;
StateSnapshot {
config ,
documents ,
.. Default ::default ( )
}
}
fn setup (
sources : & [ ( & str , & str , i32 , LanguageId ) ] ,
) -> ( StateSnapshot , Arc < Mutex < DiagnosticCollection > > , PathBuf ) {
let temp_dir = TempDir ::new ( ) . expect ( " could not create temp dir " ) ;
let location = temp_dir . path ( ) . join ( " deps " ) ;
let state_snapshot = mock_state_snapshot ( sources , & location ) ;
let collection = Arc ::new ( Mutex ::new ( DiagnosticCollection ::default ( ) ) ) ;
( state_snapshot , collection , location )
}
#[ tokio::test ]
async fn test_generate_lint_diagnostics ( ) {
let ( snapshot , collection , _ ) = setup ( & [ (
" file:///a.ts " ,
r #" import * as b from " . / b . ts " ;
2021-11-12 11:42:04 -05:00
2021-10-28 19:56:01 -04:00
let a = " a " ;
console . log ( a ) ;
" #,
1 ,
LanguageId ::TypeScript ,
) ] ) ;
let result = generate_lint_diagnostics ( & snapshot , collection ) . await ;
assert! ( result . is_ok ( ) ) ;
let diagnostics = result . unwrap ( ) ;
assert_eq! ( diagnostics . len ( ) , 1 ) ;
let ( _ , _ , diagnostics ) = & diagnostics [ 0 ] ;
assert_eq! ( diagnostics . len ( ) , 2 ) ;
}
}