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-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 ;
2020-12-07 05:46:39 -05:00
2022-06-27 16:54:09 -04:00
use crate ::args ::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 ;
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 ;
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 ;
2020-12-07 05:46:39 -05:00
use std ::collections ::HashMap ;
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
2022-03-23 09:54:22 -04:00
pub type SnapshotForDiagnostics =
2022-02-02 18:02:59 -05:00
( Arc < StateSnapshot > , Arc < ConfigSnapshot > , Option < LintConfig > ) ;
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 ( ) ;
2022-02-23 16:01:20 -05:00
let version_diagnostics =
2022-01-24 18:04:24 -05:00
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-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 ( ) ;
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 ( )
}
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 ( )
. map ( | ( specifier , version , diagnostics ) | {
( specifier . clone ( ) , ( * version , diagnostics . clone ( ) ) )
} )
. collect ( ) ;
}
}
2022-01-17 17:09:43 -05:00
#[ derive(Debug) ]
2022-03-23 09:54:22 -04:00
pub struct DiagnosticsServer {
2022-02-02 18:02:59 -05:00
channel : Option < mpsc ::UnboundedSender < SnapshotForDiagnostics > > ,
ts_diagnostics : TsDiagnosticsStore ,
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-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 ( ) ;
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 ) {
2022-02-02 18:02:59 -05:00
let ( tx , mut rx ) = mpsc ::unbounded_channel ::< SnapshotForDiagnostics > ( ) ;
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 ( ) ;
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 ( ) ;
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 ,
2022-02-02 18:02:59 -05:00
Some ( ( snapshot , config , maybe_lint_config ) ) = > {
2022-01-24 15:30:01 -05:00
// 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-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 ( ) ;
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 ( ) ;
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 ( ) {
2022-02-02 18:02:59 -05:00
ts_diagnostics_store . update ( & diagnostics ) ;
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
}
}
}
} )
} ) ;
}
2022-03-23 09:54:22 -04:00
pub fn update (
2022-02-02 18:02:59 -05:00
& self ,
message : SnapshotForDiagnostics ,
) -> 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 {
2022-02-02 18:02:59 -05:00
tx . send ( message ) . 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 )
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 ( )
} ;
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 )
2022-02-24 20:03:12 -05:00
. and_then ( | d | d . maybe_lsp_version ( ) ) ;
2022-01-29 17:50:15 -05:00
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
2022-02-04 02:14:57 -05:00
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct DiagnosticDataSpecifier {
pub specifier : ModuleSpecifier ,
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct DiagnosticDataRedirect {
pub redirect : ModuleSpecifier ,
}
2021-10-28 19:56:01 -04:00
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 ) ,
/// The import assertion type is incorrect.
InvalidAssertType ( String ) ,
/// A module requires an assertion type to be a valid import.
NoAssertType ,
/// A remote module was not found in the cache.
NoCache ( ModuleSpecifier ) ,
/// A blob module was not found in the cache.
NoCacheBlob ,
/// A data module was not found in the cache.
NoCacheData ( ModuleSpecifier ) ,
/// 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 ) ,
}
impl DenoDiagnostic {
fn code ( & self ) -> & str {
use deno_graph ::ResolutionError ;
use deno_graph ::SpecifierError ;
match self {
Self ::DenoWarn ( _ ) = > " deno-warn " ,
Self ::InvalidAssertType ( _ ) = > " invalid-assert-type " ,
Self ::NoAssertType = > " no-assert-type " ,
Self ::NoCache ( _ ) = > " no-cache " ,
Self ::NoCacheBlob = > " no-cache-blob " ,
Self ::NoCacheData ( _ ) = > " no-cache-data " ,
Self ::NoLocal ( _ ) = > " no-local " ,
Self ::Redirect { .. } = > " redirect " ,
Self ::ResolutionError ( err ) = > 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 " ,
} ,
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 ( ) {
" no-assert-type " = > lsp ::CodeAction {
title : " Insert import assertion. " . 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 : " assert { type: \" json \" } " . to_string ( ) ,
range : lsp ::Range {
start : diagnostic . range . end ,
end : diagnostic . range . end ,
} ,
} ] ,
) ] ) ) ,
.. Default ::default ( )
} ) ,
.. Default ::default ( )
} ,
" no-cache " | " no-cache-data " = > {
let data = diagnostic
. data
. clone ( )
. ok_or_else ( | | anyhow! ( " Diagnostic is missing data " ) ) ? ;
let data : DiagnosticDataSpecifier = serde_json ::from_value ( data ) ? ;
let title = if code = = " no-cache " {
format! ( " Cache \" {} \" and its dependencies. " , data . specifier )
} else {
" Cache the data URL and its dependencies. " . to_string ( )
} ;
lsp ::CodeAction {
title ,
kind : Some ( lsp ::CodeActionKind ::QUICKFIX ) ,
diagnostics : Some ( vec! [ diagnostic . clone ( ) ] ) ,
command : Some ( lsp ::Command {
title : " " . to_string ( ) ,
command : " deno.cache " . to_string ( ) ,
arguments : Some ( vec! [ json! ( [ data . specifier ] ) ] ) ,
} ) ,
.. 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 ( )
}
}
_ = > {
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
2022-03-23 09:54:22 -04:00
pub fn is_fixable ( code : & Option < lsp ::NumberOrString > ) -> bool {
2022-02-04 02:14:57 -05:00
if let Some ( lsp ::NumberOrString ::String ( code ) ) = code {
matches! (
code . as_str ( ) ,
" no-cache " | " no-cache-data " | " no-assert-type " | " redirect "
)
} 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 ) ,
Self ::InvalidAssertType ( assert_type ) = > ( lsp ::DiagnosticSeverity ::ERROR , format! ( " The module is a JSON module and expected an assertion type of \" json \" . Instead got \" {} \" . " , assert_type ) , None ) ,
Self ::NoAssertType = > ( lsp ::DiagnosticSeverity ::ERROR , " 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 ( ) , None ) ,
Self ::NoCache ( specifier ) = > ( lsp ::DiagnosticSeverity ::ERROR , format! ( " Uncached or missing remote URL: \" {} \" . " , specifier ) , Some ( json! ( { " specifier " : specifier } ) ) ) ,
Self ::NoCacheBlob = > ( lsp ::DiagnosticSeverity ::ERROR , " Uncached blob URL. " . to_string ( ) , None ) ,
Self ::NoCacheData ( specifier ) = > ( lsp ::DiagnosticSeverity ::ERROR , " Uncached data URL. " . to_string ( ) , Some ( json! ( { " specifier " : specifier } ) ) ) ,
Self ::NoLocal ( specifier ) = > ( lsp ::DiagnosticSeverity ::ERROR , format! ( " Unable to load a local module: \" {} \" . \n Please check the file path. " , specifier ) , None ) ,
Self ::Redirect { from , to } = > ( lsp ::DiagnosticSeverity ::INFORMATION , format! ( " The import of \" {} \" was redirected to \" {} \" . " , from , to ) , Some ( json! ( { " specifier " : from , " redirect " : to } ) ) ) ,
Self ::ResolutionError ( err ) = > ( lsp ::DiagnosticSeverity ::ERROR , err . to_string ( ) , None ) ,
} ;
lsp ::Diagnostic {
range : * range ,
severity : Some ( severity ) ,
code : Some ( lsp ::NumberOrString ::String ( self . code ( ) . to_string ( ) ) ) ,
source : Some ( " deno " . to_string ( ) ) ,
message ,
data ,
.. Default ::default ( )
2021-10-28 19:56:01 -04:00
}
}
}
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-04 02:14:57 -05:00
let range = documents ::to_lsp_range ( range ) ;
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-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 ( )
{
2022-02-04 02:14:57 -05:00
diagnostics
. push ( DenoDiagnostic ::DenoWarn ( message ) . to_lsp_diagnostic ( & range ) ) ;
2021-11-12 11:42:04 -05:00
}
2022-02-01 21:04:26 -05:00
}
if let Some ( doc ) = 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 {
diagnostics . push (
DenoDiagnostic ::Redirect {
from : specifier . clone ( ) ,
to : doc_specifier . clone ( ) ,
}
. to_lsp_diagnostic ( & range ) ,
) ;
}
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
2022-02-04 02:14:57 -05:00
Some ( assert_type ) = > diagnostics . push (
DenoDiagnostic ::InvalidAssertType ( assert_type . to_string ( ) )
. to_lsp_diagnostic ( & range ) ,
) ,
2021-12-15 22:53:17 -05:00
// The module is missing an assertion type, diagnostic
2022-02-04 02:14:57 -05:00
None = > diagnostics
. push ( DenoDiagnostic ::NoAssertType . to_lsp_diagnostic ( & range ) ) ,
2021-12-15 22:53:17 -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 ( ) ) ,
" data " = > DenoDiagnostic ::NoCacheData ( specifier . clone ( ) ) ,
" blob " = > DenoDiagnostic ::NoCacheBlob ,
_ = > DenoDiagnostic ::NoCache ( specifier . clone ( ) ) ,
2021-10-28 19:56:01 -04:00
} ;
2022-02-04 02:14:57 -05:00
diagnostics . push ( deno_diagnostic . to_lsp_diagnostic ( & range ) ) ;
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.
Resolved ::Err ( err ) = > diagnostics . push (
DenoDiagnostic ::ResolutionError ( err . clone ( ) )
. to_lsp_diagnostic ( & documents ::to_lsp_range ( err . range ( ) ) ) ,
) ,
2021-10-28 19:56:01 -04:00
_ = > ( ) ,
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-03-20 21:33:37 -04:00
let specifier = document . specifier ( ) ;
if config . specifier_enabled ( specifier ) {
2022-01-29 17:50:15 -05:00
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 ( (
2022-03-20 21:33:37 -04:00
specifier . clone ( ) ,
2022-01-24 15:30:01 -05:00
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 ;
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 ,
) -> 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 ( ) ,
2022-05-20 16:40:55 -04:00
( * source ) . into ( ) ,
2021-10-28 19:56:01 -04:00
) ;
}
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 (
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-01-29 17:50:15 -05:00
) -> ( StateSnapshot , PathBuf ) {
2021-10-28 19:56:01 -04:00
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 ( ) {
2022-04-01 11:15:37 -04:00
let temp_dir = TempDir ::new ( ) ;
2022-01-29 17:50:15 -05:00
let specifier = ModuleSpecifier ::parse ( " file:///a.ts " ) . unwrap ( ) ;
2022-04-01 11:15:37 -04:00
let ( snapshot , _ ) = setup (
& 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-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 ,
2022-03-20 21:33:37 -04:00
enable_paths : Vec ::new ( ) ,
2022-01-29 17:50:15 -05:00
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 ( ) {
2022-04-01 11:15:37 -04:00
let temp_dir = TempDir ::new ( ) ;
let ( snapshot , _ ) = setup (
& temp_dir ,
& [ (
" file:///a.ts " ,
r # "export let a: string = 5;"# ,
1 ,
LanguageId ::TypeScript ,
) ] ,
) ;
2022-02-02 09:25:22 -05:00
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
}