2022-01-07 22:09:52 -05:00
// Copyright 2018-2022 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 ;
2021-10-28 19:56:01 -04:00
use super ::documents ::Documents ;
2021-03-09 21:41:35 -05:00
use super ::language_server ;
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 ;
2020-12-07 05:46:39 -05:00
2022-01-19 17:10:14 -05:00
use crate ::config_file ::LintConfig ;
2020-12-07 05:46:39 -05:00
use crate ::diagnostics ;
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 ;
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 ;
2022-01-31 17:33:57 -05:00
use deno_graph ::Resolved ;
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 ;
2022-01-24 15:30:01 -05:00
use tokio_util ::sync ::CancellationToken ;
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 > ;
2022-01-24 15:30:01 -05:00
type DiagnosticMap =
HashMap < ModuleSpecifier , ( Option < i32 > , Vec < lsp ::Diagnostic > ) > ;
2021-05-11 10:43:00 -04:00
type TsDiagnosticsMap = HashMap < String , Vec < diagnostics ::Diagnostic > > ;
2022-01-24 18:04:24 -05:00
type DiagnosticsByVersionMap = HashMap < Option < i32 > , Vec < lsp ::Diagnostic > > ;
#[ derive(Clone) ]
struct DiagnosticsPublisher {
client : Client ,
all_diagnostics :
Arc < Mutex < HashMap < ModuleSpecifier , DiagnosticsByVersionMap > > > ,
}
impl DiagnosticsPublisher {
pub fn new ( client : Client ) -> Self {
Self {
client ,
all_diagnostics : Default ::default ( ) ,
}
}
pub async fn publish (
& self ,
diagnostics : DiagnosticVec ,
token : & CancellationToken ,
) {
let mut all_diagnostics = self . all_diagnostics . lock ( ) . await ;
for ( specifier , version , diagnostics ) in diagnostics {
if token . is_cancelled ( ) {
return ;
}
// the versions of all the published diagnostics should be the same, but just
// in case they're not keep track of that
let diagnostics_by_version =
all_diagnostics . entry ( specifier . clone ( ) ) . or_default ( ) ;
let mut version_diagnostics =
diagnostics_by_version . entry ( version ) . or_default ( ) ;
version_diagnostics . extend ( diagnostics ) ;
self
. client
. publish_diagnostics ( specifier , version_diagnostics . clone ( ) , version )
. await ;
}
}
pub async fn clear ( & self ) {
let mut all_diagnostics = self . all_diagnostics . lock ( ) . await ;
all_diagnostics . clear ( ) ;
}
}
2021-05-11 10:43:00 -04:00
2022-01-17 17:09:43 -05:00
#[ derive(Debug) ]
2021-05-11 10:43:00 -04:00
pub ( crate ) struct DiagnosticsServer {
channel : Option < mpsc ::UnboundedSender < ( ) > > ,
2022-01-24 15:30:01 -05:00
ts_diagnostics : Arc < Mutex < DiagnosticMap > > ,
2022-01-17 17:09:43 -05:00
client : Client ,
performance : Arc < Performance > ,
ts_server : Arc < TsServer > ,
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 > ,
) -> 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 ,
}
}
2022-01-24 15:30:01 -05:00
pub ( crate ) async 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-01-24 15:30:01 -05:00
let ts_diagnostics = self . ts_diagnostics . lock ( ) . await ;
if let Some ( ( diagnostics_doc_version , diagnostics ) ) =
ts_diagnostics . get ( specifier )
{
// only get the diagnostics if they're up to date
if document_version = = * diagnostics_doc_version {
return diagnostics . clone ( ) ;
}
}
Vec ::new ( )
2021-05-11 10:43:00 -04:00
}
2021-06-03 07:13:53 -04:00
pub ( crate ) async fn invalidate ( & self , specifiers : Vec < ModuleSpecifier > ) {
2022-01-24 15:30:01 -05:00
let mut ts_diagnostics = self . ts_diagnostics . lock ( ) . await ;
2021-07-25 01:33:42 -04:00
for specifier in & specifiers {
2022-01-24 15:30:01 -05:00
ts_diagnostics . 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 ) {
2022-01-24 15:30:01 -05:00
let mut ts_diagnostics = self . ts_diagnostics . lock ( ) . await ;
ts_diagnostics . clear ( ) ;
2021-07-25 01:33:42 -04:00
}
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 > > ,
2021-03-09 21:41:35 -05:00
) {
2021-05-11 10:43:00 -04:00
let ( tx , mut rx ) = mpsc ::unbounded_channel ::< ( ) > ( ) ;
self . channel = Some ( tx ) ;
2022-01-17 17:09:43 -05:00
let client = self . client . clone ( ) ;
let performance = self . performance . clone ( ) ;
2022-01-24 15:30:01 -05:00
let stored_ts_diagnostics = 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 ( ) ;
let mut ts_handle : Option < tokio ::task ::JoinHandle < ( ) > > = None ;
let mut lint_handle : Option < tokio ::task ::JoinHandle < ( ) > > = None ;
let mut deps_handle : Option < tokio ::task ::JoinHandle < ( ) > > = None ;
2022-01-24 18:04:24 -05:00
let diagnostics_publisher = DiagnosticsPublisher ::new ( client . 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 ,
Some ( ( ) ) = > {
// cancel the previous run
token . cancel ( ) ;
token = CancellationToken ::new ( ) ;
2022-01-24 18:04:24 -05:00
diagnostics_publisher . clear ( ) . await ;
2021-03-18 16:26:41 -04:00
2022-01-19 17:10:14 -05:00
let ( snapshot , config , maybe_lint_config ) = {
let language_server = language_server . lock ( ) . await ;
(
language_server . snapshot ( ) ,
language_server . config . snapshot ( ) ,
language_server . maybe_lint_config . clone ( ) ,
)
} ;
2022-01-24 15:30:01 -05:00
let previous_ts_handle = ts_handle . take ( ) ;
ts_handle = Some ( tokio ::spawn ( {
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 ( ) ;
let stored_ts_diagnostics = stored_ts_diagnostics . clone ( ) ;
let snapshot = snapshot . clone ( ) ;
let config = config . clone ( ) ;
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 ) = > { }
} ;
let mark =
performance . mark ( " update_diagnostics_ts " , None ::< ( ) > ) ;
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 | {
error! ( " Error generating TypeScript diagnostics: {} " , err ) ;
} )
. unwrap_or_default ( ) ;
2022-01-24 15:30:01 -05:00
if ! token . is_cancelled ( ) {
{
let mut stored_ts_diagnostics =
stored_ts_diagnostics . lock ( ) . await ;
* stored_ts_diagnostics = diagnostics
. iter ( )
. map ( | ( specifier , version , diagnostics ) | {
( specifier . clone ( ) , ( * version , diagnostics . clone ( ) ) )
} )
. collect ( ) ;
}
2022-01-24 18:04:24 -05:00
diagnostics_publisher . publish ( diagnostics , & token ) . await ;
if ! token . is_cancelled ( ) {
performance . measure ( mark ) ;
2022-01-24 15:30:01 -05:00
}
}
}
} ) ) ;
let previous_deps_handle = deps_handle . take ( ) ;
deps_handle = Some ( tokio ::spawn ( {
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 ( ) ;
async move {
if let Some ( previous_handle ) = previous_deps_handle {
previous_handle . await ;
}
let mark =
performance . mark ( " update_diagnostics_deps " , None ::< ( ) > ) ;
let diagnostics = generate_deps_diagnostics (
2022-01-29 17:50:15 -05:00
& snapshot ,
& config ,
2022-01-24 15:30:01 -05:00
token . clone ( ) ,
)
. await ;
2022-01-24 18:04:24 -05:00
diagnostics_publisher . publish ( diagnostics , & token ) . await ;
2022-01-24 15:30:01 -05:00
if ! token . is_cancelled ( ) {
performance . measure ( mark ) ;
}
}
} ) ) ;
let previous_lint_handle = lint_handle . take ( ) ;
lint_handle = Some ( tokio ::spawn ( {
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 ( ) ;
async move {
if let Some ( previous_handle ) = previous_lint_handle {
previous_handle . await ;
}
let mark =
performance . mark ( " update_diagnostics_lint " , None ::< ( ) > ) ;
let diagnostics = generate_lint_diagnostics (
& snapshot ,
& config ,
maybe_lint_config ,
token . clone ( ) ,
)
. await ;
2022-01-24 18:04:24 -05:00
diagnostics_publisher . publish ( diagnostics , & token ) . await ;
2022-01-24 15:30:01 -05:00
if ! token . is_cancelled ( ) {
performance . measure ( mark ) ;
}
}
} ) ) ;
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-11-24 20:10:12 -05:00
diagnostics ::DiagnosticCategory ::Error = > lsp ::DiagnosticSeverity ::ERROR ,
2020-12-21 08:44:26 -05:00
diagnostics ::DiagnosticCategory ::Warning = > {
2021-11-24 20:10:12 -05:00
lsp ::DiagnosticSeverity ::WARNING
2020-12-21 08:44:26 -05:00
}
diagnostics ::DiagnosticCategory ::Suggestion = > {
2021-11-24 20:10:12 -05:00
lsp ::DiagnosticSeverity ::HINT
2020-12-21 08:44:26 -05:00
}
diagnostics ::DiagnosticCategory ::Message = > {
2021-11-24 20:10:12 -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
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
}
2021-05-11 10:43:00 -04:00
async fn generate_lint_diagnostics (
snapshot : & language_server ::StateSnapshot ,
2022-01-19 17:10:14 -05:00
config : & ConfigSnapshot ,
maybe_lint_config : Option < LintConfig > ,
2022-01-24 15:30:01 -05:00
token : CancellationToken ,
) -> DiagnosticVec {
2021-11-24 15:14:19 -05:00
let documents = snapshot . documents . documents ( true , true ) ;
2022-01-19 17:10:14 -05:00
let workspace_settings = config . settings . workspace . clone ( ) ;
2021-11-22 18:10:33 -05:00
2022-01-24 15:30:01 -05:00
let mut diagnostics_vec = Vec ::new ( ) ;
if workspace_settings . lint {
for document in documents {
// exit early if cancelled
if token . is_cancelled ( ) {
break ;
}
let version = document . maybe_lsp_version ( ) ;
diagnostics_vec . push ( (
document . specifier ( ) . clone ( ) ,
version ,
2022-01-29 17:50:15 -05:00
generate_document_lint_diagnostics (
config ,
& maybe_lint_config ,
& document ,
) ,
2022-01-24 15:30:01 -05:00
) ) ;
2021-01-22 05:03:16 -05:00
}
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 ,
maybe_lint_config : & Option < LintConfig > ,
document : & Document ,
) -> Vec < lsp ::Diagnostic > {
if ! config . specifier_enabled ( document . specifier ( ) ) {
return Vec ::new ( ) ;
}
if let Some ( lint_config ) = & maybe_lint_config {
if ! lint_config . files . matches_specifier ( document . specifier ( ) ) {
return Vec ::new ( ) ;
}
}
match document . maybe_parsed_source ( ) {
Some ( Ok ( parsed_source ) ) = > {
if let Ok ( references ) = analysis ::get_lint_references (
& parsed_source ,
maybe_lint_config . as_ref ( ) ,
) {
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
. documents ( true , true )
. iter ( )
. map ( | d | d . specifier ( ) . clone ( ) )
. collect ::< Vec < _ > > ( ) ;
2022-01-29 17:50:15 -05:00
let ( enabled_specifiers , disabled_specifiers ) = specifiers
. iter ( )
. cloned ( )
. partition ::< Vec < _ > , _ > ( | s | config . specifier_enabled ( s ) ) ;
let ts_diagnostics_map : TsDiagnosticsMap = if ! enabled_specifiers . is_empty ( ) {
let req = tsc ::RequestMethod ::GetDiagnostics ( enabled_specifiers ) ;
2022-02-02 09:25:22 -05:00
ts_server
. request_with_cancellation ( snapshot . clone ( ) , req , token )
. 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 )
. map ( | d | d . maybe_lsp_version ( ) )
. flatten ( ) ;
// 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 ( )
} ;
diagnostics_vec . push ( ( specifier , version , ts_diagnostics ) ) ;
}
// 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 )
. map ( | d | d . maybe_lsp_version ( ) )
. flatten ( ) ;
diagnostics_vec . push ( ( specifier , version , 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
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 {
2022-01-31 17:33:57 -05:00
ResolutionError ::InvalidDowngrade { .. } = > {
2021-10-28 19:56:01 -04:00
lsp ::NumberOrString ::String ( " invalid-downgrade " . to_string ( ) )
}
2022-01-31 17:33:57 -05:00
ResolutionError ::InvalidLocalImport { .. } = > {
2021-10-28 19:56:01 -04:00
lsp ::NumberOrString ::String ( " invalid-local-import " . to_string ( ) )
}
2022-01-31 17:33:57 -05:00
ResolutionError ::InvalidSpecifier { error , .. } = > match error {
2021-10-28 19:56:01 -04:00
SpecifierError ::ImportPrefixMissing ( _ , _ ) = > {
lsp ::NumberOrString ::String ( " import-prefix-missing " . to_string ( ) )
}
SpecifierError ::InvalidUrl ( _ ) = > {
lsp ::NumberOrString ::String ( " invalid-url " . to_string ( ) )
}
} ,
2022-01-31 17:33:57 -05:00
ResolutionError ::ResolverError { .. } = > {
2021-10-28 19:56:01 -04:00
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 ,
2022-02-01 21:04:26 -05:00
cache_metadata : & cache ::CacheMetadata ,
2021-10-28 19:56:01 -04:00
resolved : & deno_graph ::Resolved ,
2021-12-15 22:53:17 -05:00
is_dynamic : bool ,
maybe_assert_type : Option < & str > ,
2021-05-24 22:34:01 -04:00
) {
2021-10-28 19:56:01 -04:00
match resolved {
2022-01-31 17:33:57 -05:00
Resolved ::Ok {
specifier , range , ..
} = > {
2022-02-01 21:04:26 -05:00
if let Some ( metadata ) = cache_metadata . get ( specifier ) {
if let Some ( message ) =
metadata . get ( & cache ::MetadataKey ::Warning ) . cloned ( )
{
2021-11-12 11:42:04 -05:00
diagnostics . push ( lsp ::Diagnostic {
range : documents ::to_lsp_range ( range ) ,
2021-11-24 20:10:12 -05:00
severity : Some ( lsp ::DiagnosticSeverity ::WARNING ) ,
2021-11-12 11:42:04 -05:00
code : Some ( lsp ::NumberOrString ::String ( " deno-warn " . to_string ( ) ) ) ,
source : Some ( " deno " . to_string ( ) ) ,
message ,
.. Default ::default ( )
2022-02-01 21:04:26 -05:00
} ) ;
2021-11-12 11:42:04 -05:00
}
2022-02-01 21:04:26 -05:00
}
if let Some ( doc ) = documents . get ( specifier ) {
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 " ) = > ( ) ,
// The dynamic import statement is missing an assertion type, which
// 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
Some ( assert_type ) = > diagnostics . push ( lsp ::Diagnostic {
range : documents ::to_lsp_range ( range ) ,
severity : Some ( lsp ::DiagnosticSeverity ::ERROR ) ,
code : Some ( lsp ::NumberOrString ::String ( " invalid-assert-type " . to_string ( ) ) ) ,
source : Some ( " deno " . to_string ( ) ) ,
message : format ! ( " The module is a JSON module and expected an assertion type of \" json \" . Instead got \" {} \" . " , assert_type ) ,
.. Default ::default ( )
} ) ,
// The module is missing an assertion type, diagnostic
None = > diagnostics . push ( lsp ::Diagnostic {
range : documents ::to_lsp_range ( range ) ,
severity : Some ( lsp ::DiagnosticSeverity ::ERROR ) ,
code : Some ( lsp ::NumberOrString ::String ( " no-assert-type " . to_string ( ) ) ) ,
source : Some ( " deno " . to_string ( ) ) ,
message : " The module is a JSON module and not being imported with an import assertion. Consider adding `assert { type: \" json \" }` to the import statement. " . to_string ( ) ,
.. Default ::default ( )
} ) ,
}
}
2021-11-12 11:42:04 -05:00
} 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-11-24 20:10:12 -05: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
}
}
2022-01-31 17:33:57 -05:00
Resolved ::Err ( err ) = > diagnostics . push ( lsp ::Diagnostic {
2021-10-28 19:56:01 -04:00
range : documents ::to_lsp_range ( err . range ( ) ) ,
2021-11-24 20:10:12 -05:00
severity : Some ( lsp ::DiagnosticSeverity ::ERROR ) ,
2021-10-28 19:56:01 -04:00
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 (
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
2022-01-24 15:30:01 -05:00
for document in snapshot . documents . documents ( true , true ) {
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-01-29 17:50:15 -05:00
if config . specifier_enabled ( document . specifier ( ) ) {
for ( _ , dependency ) in document . dependencies ( ) {
diagnose_dependency (
& mut diagnostics ,
& snapshot . documents ,
2022-02-01 21:04:26 -05:00
& snapshot . cache_metadata ,
2022-01-29 17:50:15 -05:00
& dependency . maybe_code ,
dependency . is_dynamic ,
dependency . maybe_assert_type . as_deref ( ) ,
) ;
diagnose_dependency (
& mut diagnostics ,
& snapshot . documents ,
2022-02-01 21:04:26 -05:00
& snapshot . cache_metadata ,
2022-01-29 17:50:15 -05:00
& dependency . maybe_type ,
dependency . is_dynamic ,
dependency . maybe_assert_type . as_deref ( ) ,
) ;
}
2021-05-11 10:43:00 -04:00
}
2022-01-24 15:30:01 -05:00
diagnostics_vec . push ( (
document . specifier ( ) . clone ( ) ,
document . maybe_lsp_version ( ) ,
diagnostics ,
) ) ;
}
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 ::* ;
use crate ::lsp ::config ::ConfigSnapshot ;
use crate ::lsp ::config ::Settings ;
2022-01-29 17:50:15 -05:00
use crate ::lsp ::config ::SpecifierSettings ;
2021-10-28 19:56:01 -04:00
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 ( ) ) ,
) ;
}
2022-01-19 17:10:14 -05:00
StateSnapshot {
documents ,
.. Default ::default ( )
}
}
fn mock_config ( ) -> ConfigSnapshot {
ConfigSnapshot {
2021-10-28 19:56:01 -04:00
settings : Settings {
workspace : WorkspaceSettings {
enable : true ,
lint : true ,
.. Default ::default ( )
} ,
.. Default ::default ( )
} ,
.. Default ::default ( )
}
}
fn setup (
sources : & [ ( & str , & str , i32 , LanguageId ) ] ,
2022-01-29 17:50:15 -05:00
) -> ( StateSnapshot , PathBuf ) {
2021-10-28 19:56:01 -04:00
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 ) ;
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 ( ) {
let specifier = ModuleSpecifier ::parse ( " file:///a.ts " ) . unwrap ( ) ;
let ( snapshot , _ ) = setup ( & [ (
2021-10-28 19:56:01 -04:00
" 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
" #,
1 ,
LanguageId ::TypeScript ,
) ] ) ;
2022-01-29 17:50:15 -05:00
let snapshot = Arc ::new ( snapshot ) ;
let ts_server = TsServer ::new ( Default ::default ( ) ) ;
// test enabled
{
let enabled_config = mock_config ( ) ;
let diagnostics = generate_lint_diagnostics (
& snapshot ,
& enabled_config ,
None ,
Default ::default ( ) ,
)
. await ;
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 ( ) ;
2022-01-29 17:50:15 -05:00
assert_eq! ( get_diagnostics_for_single ( diagnostics ) . len ( ) , 4 ) ;
let diagnostics = generate_deps_diagnostics (
& snapshot ,
& enabled_config ,
Default ::default ( ) ,
)
. await ;
assert_eq! ( get_diagnostics_for_single ( diagnostics ) . len ( ) , 1 ) ;
}
// now test disabled specifier
{
let mut disabled_config = mock_config ( ) ;
disabled_config . settings . specifiers . insert (
specifier . clone ( ) ,
(
specifier . clone ( ) ,
SpecifierSettings {
enable : false ,
code_lens : Default ::default ( ) ,
} ,
) ,
) ;
let diagnostics = generate_lint_diagnostics (
& snapshot ,
& disabled_config ,
None ,
Default ::default ( ) ,
)
. await ;
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 ) ;
let diagnostics = generate_deps_diagnostics (
& snapshot ,
& disabled_config ,
Default ::default ( ) ,
)
. await ;
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 ) ;
let ( _ , _ , diagnostics ) = diagnostic_vec . into_iter ( ) . next ( ) . unwrap ( ) ;
diagnostics
2021-10-28 19:56:01 -04:00
}
2022-02-02 09:25:22 -05:00
#[ tokio::test ]
async fn test_cancelled_ts_diagnostics_request ( ) {
let specifier = ModuleSpecifier ::parse ( " file:///a.ts " ) . unwrap ( ) ;
let ( snapshot , _ ) = setup ( & [ (
" file:///a.ts " ,
r # "export let a: string = 5;"# ,
1 ,
LanguageId ::TypeScript ,
) ] ) ;
let snapshot = Arc ::new ( snapshot ) ;
let ts_server = TsServer ::new ( Default ::default ( ) ) ;
let config = mock_config ( ) ;
let token = CancellationToken ::new ( ) ;
token . cancel ( ) ;
let diagnostics =
generate_ts_diagnostics ( snapshot . clone ( ) , & config , & ts_server , token )
. await
. unwrap ( ) ;
// should be none because it's cancelled
assert_eq! ( diagnostics . len ( ) , 0 ) ;
}
2021-10-28 19:56:01 -04:00
}