2023-01-02 16:00:42 -05:00
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
2020-02-24 14:48:14 -05:00
2021-09-12 12:04:17 -04:00
use deno_runtime ::colors ;
2020-09-12 05:53:57 -04:00
2020-11-01 21:51:56 -05:00
use deno_core ::serde ::Deserialize ;
use deno_core ::serde ::Deserializer ;
use deno_core ::serde ::Serialize ;
use deno_core ::serde ::Serializer ;
2023-04-12 21:08:01 -04:00
use lazy_regex ::lazy_regex ;
2021-12-18 16:14:42 -05:00
use once_cell ::sync ::Lazy ;
2020-09-12 05:53:57 -04:00
use regex ::Regex ;
2019-07-10 18:53:48 -04:00
use std ::error ::Error ;
2019-06-04 09:03:56 -04:00
use std ::fmt ;
2020-09-12 05:53:57 -04:00
const MAX_SOURCE_LINE_LENGTH : usize = 150 ;
const UNSTABLE_DENO_PROPS : & [ & str ] = & [
2021-04-09 20:41:59 -04:00
" CreateHttpClientOptions " ,
2020-09-12 05:53:57 -04:00
" DatagramConn " ,
" HttpClient " ,
" UnixConnectOptions " ,
" UnixListenOptions " ,
" connect " ,
" createHttpClient " ,
" kill " ,
" listen " ,
" listenDatagram " ,
2021-08-06 17:28:10 -04:00
" dlopen " ,
2021-11-10 08:30:20 -05:00
" removeSignalListener " ,
2020-09-12 05:53:57 -04:00
" shutdown " ,
" umask " ,
2022-08-18 08:05:02 -04:00
" serve " ,
" ServeInit " ,
" ServeTlsInit " ,
" Handler " ,
2022-12-26 18:16:12 -05:00
" osUptime " ,
2020-09-12 05:53:57 -04:00
] ;
2023-04-12 21:08:01 -04:00
static MSG_MISSING_PROPERTY_DENO : Lazy < Regex > =
lazy_regex! ( r # "Property '([^']+)' does not exist on type 'typeof Deno'"# ) ;
2021-12-18 16:14:42 -05:00
static MSG_SUGGESTION : Lazy < Regex > =
2023-04-12 21:08:01 -04:00
lazy_regex! ( r # " Did you mean '([^']+)'\?"# ) ;
2019-06-04 09:03:56 -04:00
2020-09-12 05:53:57 -04:00
/// Potentially convert a "raw" diagnostic message from TSC to something that
/// provides a more sensible error message given a Deno runtime context.
fn format_message ( msg : & str , code : & u64 ) -> String {
match code {
2339 = > {
if let Some ( captures ) = MSG_MISSING_PROPERTY_DENO . captures ( msg ) {
if let Some ( property ) = captures . get ( 1 ) {
if UNSTABLE_DENO_PROPS . contains ( & property . as_str ( ) ) {
return format! ( " {} 'Deno. {} ' is an unstable API. Did you forget to run with the '--unstable' flag? " , msg , property . as_str ( ) ) ;
}
}
2019-06-04 09:03:56 -04:00
}
2020-09-12 05:53:57 -04:00
msg . to_string ( )
2019-06-04 09:03:56 -04:00
}
2020-09-12 05:53:57 -04:00
2551 = > {
if let ( Some ( caps_property ) , Some ( caps_suggestion ) ) = (
MSG_MISSING_PROPERTY_DENO . captures ( msg ) ,
MSG_SUGGESTION . captures ( msg ) ,
) {
if let ( Some ( property ) , Some ( suggestion ) ) =
( caps_property . get ( 1 ) , caps_suggestion . get ( 1 ) )
{
if UNSTABLE_DENO_PROPS . contains ( & property . as_str ( ) ) {
return format! ( " {} 'Deno. {} ' is an unstable API. Did you forget to run with the '--unstable' flag, or did you mean ' {} '? " , MSG_SUGGESTION . replace ( msg , " " ) , property . as_str ( ) , suggestion . as_str ( ) ) ;
}
}
}
2019-06-04 09:03:56 -04:00
2020-09-12 05:53:57 -04:00
msg . to_string ( )
}
_ = > msg . to_string ( ) ,
2019-06-04 09:03:56 -04:00
}
}
2020-10-13 19:52:49 -04:00
#[ derive(Clone, Debug, Eq, PartialEq) ]
2020-09-12 05:53:57 -04:00
pub enum DiagnosticCategory {
Warning ,
Error ,
Suggestion ,
Message ,
2019-07-10 18:53:48 -04:00
}
2020-09-12 05:53:57 -04:00
impl fmt ::Display for DiagnosticCategory {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
write! (
f ,
" {} " ,
match self {
DiagnosticCategory ::Warning = > " WARN " ,
DiagnosticCategory ::Error = > " ERROR " ,
DiagnosticCategory ::Suggestion = > " " ,
DiagnosticCategory ::Message = > " " ,
}
)
}
}
2019-06-04 09:03:56 -04:00
2020-09-12 05:53:57 -04:00
impl < ' de > Deserialize < ' de > for DiagnosticCategory {
fn deserialize < D > ( deserializer : D ) -> Result < Self , D ::Error >
where
D : Deserializer < ' de > ,
{
let s : i64 = Deserialize ::deserialize ( deserializer ) ? ;
Ok ( DiagnosticCategory ::from ( s ) )
}
}
2019-06-04 09:03:56 -04:00
2020-11-01 21:51:56 -05:00
impl Serialize for DiagnosticCategory {
fn serialize < S > ( & self , serializer : S ) -> Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
let value = match self {
2020-11-27 14:47:35 -05:00
DiagnosticCategory ::Warning = > 0_ i32 ,
DiagnosticCategory ::Error = > 1_ i32 ,
DiagnosticCategory ::Suggestion = > 2_ i32 ,
DiagnosticCategory ::Message = > 3_ i32 ,
2020-11-01 21:51:56 -05:00
} ;
Serialize ::serialize ( & value , serializer )
}
}
2020-09-12 05:53:57 -04:00
impl From < i64 > for DiagnosticCategory {
fn from ( value : i64 ) -> Self {
match value {
0 = > DiagnosticCategory ::Warning ,
1 = > DiagnosticCategory ::Error ,
2 = > DiagnosticCategory ::Suggestion ,
3 = > DiagnosticCategory ::Message ,
2023-01-27 10:43:16 -05:00
_ = > panic! ( " Unknown value: {value} " ) ,
2020-09-12 05:53:57 -04:00
}
}
2019-06-04 09:03:56 -04:00
}
2020-11-01 21:51:56 -05:00
#[ derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq) ]
2020-09-12 05:53:57 -04:00
#[ serde(rename_all = " camelCase " ) ]
pub struct DiagnosticMessageChain {
message_text : String ,
category : DiagnosticCategory ,
2020-04-20 15:39:02 -04:00
code : i64 ,
2020-09-12 05:53:57 -04:00
next : Option < Vec < DiagnosticMessageChain > > ,
2020-04-20 15:39:02 -04:00
}
2020-09-12 05:53:57 -04:00
impl DiagnosticMessageChain {
pub fn format_message ( & self , level : usize ) -> String {
let mut s = String ::new ( ) ;
2019-06-04 09:03:56 -04:00
2021-07-30 09:03:41 -04:00
s . push_str ( & " " . repeat ( level * 2 ) ) ;
2020-09-12 05:53:57 -04:00
s . push_str ( & self . message_text ) ;
if let Some ( next ) = & self . next {
s . push ( '\n' ) ;
let arr = next . clone ( ) ;
for dm in arr {
s . push_str ( & dm . format_message ( level + 1 ) ) ;
}
}
2019-06-04 09:03:56 -04:00
2020-05-16 09:41:32 -04:00
s
}
2020-04-20 15:39:02 -04:00
}
2019-06-04 09:03:56 -04:00
2020-11-01 21:51:56 -05:00
#[ derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq) ]
2020-09-12 05:53:57 -04:00
#[ serde(rename_all = " camelCase " ) ]
pub struct Position {
pub line : u64 ,
pub character : u64 ,
}
2019-06-04 09:03:56 -04:00
2020-11-01 21:51:56 -05:00
#[ derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq) ]
2020-09-12 05:53:57 -04:00
#[ serde(rename_all = " camelCase " ) ]
pub struct Diagnostic {
2020-10-13 19:52:49 -04:00
pub category : DiagnosticCategory ,
pub code : u64 ,
pub start : Option < Position > ,
pub end : Option < Position > ,
pub message_text : Option < String > ,
pub message_chain : Option < DiagnosticMessageChain > ,
pub source : Option < String > ,
pub source_line : Option < String > ,
pub file_name : Option < String > ,
pub related_information : Option < Vec < Diagnostic > > ,
2020-04-20 15:39:02 -04:00
}
2019-06-04 09:03:56 -04:00
2020-09-12 05:53:57 -04:00
impl Diagnostic {
fn fmt_category_and_code ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
let category = match self . category {
DiagnosticCategory ::Error = > " ERROR " ,
DiagnosticCategory ::Warning = > " WARN " ,
_ = > " " ,
} ;
2021-06-21 17:27:32 -04:00
let code = if self . code > = 900001 {
" " . to_string ( )
} else {
colors ::bold ( format! ( " TS {} " , self . code ) ) . to_string ( )
} ;
2020-09-12 05:53:57 -04:00
if ! category . is_empty ( ) {
2023-01-27 10:43:16 -05:00
write! ( f , " {code}[{category}]: " )
2020-09-12 05:53:57 -04:00
} else {
Ok ( ( ) )
}
2019-06-04 09:03:56 -04:00
}
2020-09-12 05:53:57 -04:00
fn fmt_frame ( & self , f : & mut fmt ::Formatter , level : usize ) -> fmt ::Result {
if let ( Some ( file_name ) , Some ( start ) ) =
( self . file_name . as_ref ( ) , self . start . as_ref ( ) )
{
write! (
f ,
" \n {:indent$} at {}:{}:{} " ,
" " ,
colors ::cyan ( file_name ) ,
colors ::yellow ( & ( start . line + 1 ) . to_string ( ) ) ,
colors ::yellow ( & ( start . character + 1 ) . to_string ( ) ) ,
indent = level
)
} else {
Ok ( ( ) )
2020-05-16 09:41:32 -04:00
}
2019-06-04 09:03:56 -04:00
}
2020-09-12 05:53:57 -04:00
fn fmt_message ( & self , f : & mut fmt ::Formatter , level : usize ) -> fmt ::Result {
if let Some ( message_chain ) = & self . message_chain {
write! ( f , " {} " , message_chain . format_message ( level ) )
} else {
write! (
f ,
" {:indent$}{} " ,
" " ,
format_message ( & self . message_text . clone ( ) . unwrap ( ) , & self . code ) ,
indent = level ,
2020-04-20 15:39:02 -04:00
)
2020-09-12 05:53:57 -04:00
}
2019-06-04 09:03:56 -04:00
}
2020-09-12 05:53:57 -04:00
fn fmt_source_line (
& self ,
f : & mut fmt ::Formatter ,
level : usize ,
) -> fmt ::Result {
if let ( Some ( source_line ) , Some ( start ) , Some ( end ) ) =
( & self . source_line , & self . start , & self . end )
{
if ! source_line . is_empty ( ) & & source_line . len ( ) < = MAX_SOURCE_LINE_LENGTH
{
write! ( f , " \n {:indent$}{} " , " " , source_line , indent = level ) ? ;
let length = if start . line = = end . line {
end . character - start . character
} else {
1
} ;
let mut s = String ::new ( ) ;
for i in 0 .. start . character {
s . push ( if source_line . chars ( ) . nth ( i as usize ) . unwrap ( ) = = '\t' {
'\t'
} else {
' '
} ) ;
}
// TypeScript always uses `~` when underlining, but v8 always uses `^`.
// We will use `^` to indicate a single point, or `~` when spanning
// multiple characters.
let ch = if length > 1 { '~' } else { '^' } ;
for _i in 0 .. length {
s . push ( ch )
}
let underline = if self . is_error ( ) {
colors ::red ( & s ) . to_string ( )
} else {
colors ::cyan ( & s ) . to_string ( )
} ;
write! ( f , " \n {:indent$}{} " , " " , underline , indent = level ) ? ;
}
}
2019-06-04 09:03:56 -04:00
2020-09-12 05:53:57 -04:00
Ok ( ( ) )
}
2019-09-17 12:24:44 -04:00
2020-09-12 05:53:57 -04:00
fn fmt_related_information ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
if let Some ( related_information ) = self . related_information . as_ref ( ) {
2023-01-24 09:05:54 -05:00
if ! related_information . is_empty ( ) {
write! ( f , " \n \n " ) ? ;
for info in related_information {
info . fmt_stack ( f , 4 ) ? ;
}
2019-09-17 12:24:44 -04:00
}
}
2020-09-12 05:53:57 -04:00
Ok ( ( ) )
}
fn fmt_stack ( & self , f : & mut fmt ::Formatter , level : usize ) -> fmt ::Result {
self . fmt_category_and_code ( f ) ? ;
self . fmt_message ( f , level ) ? ;
self . fmt_source_line ( f , level ) ? ;
self . fmt_frame ( f , level )
}
fn is_error ( & self ) -> bool {
self . category = = DiagnosticCategory ::Error
2019-06-04 09:03:56 -04:00
}
}
2020-09-12 05:53:57 -04:00
impl fmt ::Display for Diagnostic {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
self . fmt_stack ( f , 0 ) ? ;
self . fmt_related_information ( f )
}
2019-06-04 09:03:56 -04:00
}
2020-10-27 20:52:20 -04:00
#[ derive(Clone, Debug, Default, Eq, PartialEq) ]
pub struct Diagnostics ( Vec < Diagnostic > ) ;
impl Diagnostics {
#[ cfg(test) ]
pub fn new ( diagnostics : Vec < Diagnostic > ) -> Self {
Diagnostics ( diagnostics )
}
2021-11-29 17:23:30 -05:00
/// Return a set of diagnostics where only the values where the predicate
/// returns `true` are included.
pub fn filter < P > ( & self , predicate : P ) -> Self
where
2023-01-04 07:20:36 -05:00
P : FnMut ( & Diagnostic ) -> Option < Diagnostic > ,
2021-11-29 17:23:30 -05:00
{
2023-01-04 07:20:36 -05:00
let diagnostics = self . 0. iter ( ) . filter_map ( predicate ) . collect ( ) ;
2021-11-29 17:23:30 -05:00
Self ( diagnostics )
}
2020-10-27 20:52:20 -04:00
pub fn is_empty ( & self ) -> bool {
self . 0. is_empty ( )
}
}
2020-09-12 05:53:57 -04:00
impl < ' de > Deserialize < ' de > for Diagnostics {
2020-05-05 12:23:15 -04:00
fn deserialize < D > ( deserializer : D ) -> Result < Self , D ::Error >
where
D : Deserializer < ' de > ,
{
2020-09-12 05:53:57 -04:00
let items : Vec < Diagnostic > = Deserialize ::deserialize ( deserializer ) ? ;
Ok ( Diagnostics ( items ) )
2020-05-05 12:23:15 -04:00
}
}
2020-11-01 21:51:56 -05:00
impl Serialize for Diagnostics {
fn serialize < S > ( & self , serializer : S ) -> Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
Serialize ::serialize ( & self . 0 , serializer )
}
}
2020-09-12 05:53:57 -04:00
impl fmt ::Display for Diagnostics {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
let mut i = 0 ;
for item in & self . 0 {
if i > 0 {
write! ( f , " \n \n " ) ? ;
}
2023-01-27 10:43:16 -05:00
write! ( f , " {item} " ) ? ;
2020-09-12 05:53:57 -04:00
i + = 1 ;
2019-06-04 09:03:56 -04:00
}
2020-09-12 05:53:57 -04:00
if i > 1 {
2023-01-27 10:43:16 -05:00
write! ( f , " \n \n Found {i} errors. " ) ? ;
2020-09-12 05:53:57 -04:00
}
Ok ( ( ) )
2019-06-04 09:03:56 -04:00
}
}
2020-09-12 05:53:57 -04:00
impl Error for Diagnostics { }
2019-06-04 09:03:56 -04:00
#[ cfg(test) ]
mod tests {
use super ::* ;
2020-09-21 12:36:37 -04:00
use deno_core ::serde_json ;
use deno_core ::serde_json ::json ;
2021-09-12 12:04:17 -04:00
use test_util ::strip_ansi_codes ;
2019-06-04 09:03:56 -04:00
2020-09-12 05:53:57 -04:00
#[ test ]
fn test_de_diagnostics ( ) {
let value = json! ( [
{
" messageText " : " Unknown compiler option 'invalid'. " ,
" category " : 1 ,
" code " : 5023
} ,
{
" start " : {
" line " : 0 ,
" character " : 0
2019-06-04 09:03:56 -04:00
} ,
2020-09-12 05:53:57 -04:00
" end " : {
" line " : 0 ,
" character " : 7
2019-06-04 09:03:56 -04:00
} ,
2020-09-12 05:53:57 -04:00
" fileName " : " test.ts " ,
" messageText " : " Cannot find name 'console'. Do you need to change your target library? Try changing the `lib` compiler option to include 'dom'. " ,
" sourceLine " : " console.log( \" a \" ); " ,
" category " : 1 ,
" code " : 2584
} ,
{
" start " : {
" line " : 7 ,
" character " : 0
} ,
" end " : {
" line " : 7 ,
" character " : 7
} ,
" fileName " : " test.ts " ,
" messageText " : " Cannot find name 'foo_Bar'. Did you mean 'foo_bar'? " ,
" sourceLine " : " foo_Bar(); " ,
" relatedInformation " : [
2019-06-04 09:03:56 -04:00
{
2020-09-12 05:53:57 -04:00
" start " : {
" line " : 3 ,
" character " : 9
} ,
" end " : {
" line " : 3 ,
" character " : 16
} ,
" fileName " : " test.ts " ,
" messageText " : " 'foo_bar' is declared here. " ,
" sourceLine " : " function foo_bar() { " ,
" category " : 3 ,
" code " : 2728
}
] ,
" category " : 1 ,
" code " : 2552
} ,
{
" start " : {
" line " : 18 ,
" character " : 0
} ,
" end " : {
" line " : 18 ,
" character " : 1
} ,
" fileName " : " test.ts " ,
" messageChain " : {
" messageText " : " Type '{ a: { b: { c(): { d: number; }; }; }; }' is not assignable to type '{ a: { b: { c(): { d: string; }; }; }; }'. " ,
" category " : 1 ,
" code " : 2322 ,
" next " : [
{
" messageText " : " The types of 'a.b.c().d' are incompatible between these types. " ,
" category " : 1 ,
" code " : 2200 ,
2019-09-17 12:24:44 -04:00
" next " : [
{
2020-09-12 05:53:57 -04:00
" messageText " : " Type 'number' is not assignable to type 'string'. " ,
" category " : 1 ,
" code " : 2322
2019-06-04 09:03:56 -04:00
}
2019-09-17 12:24:44 -04:00
]
2020-09-12 05:53:57 -04:00
}
]
} ,
" sourceLine " : " x = y; " ,
" code " : 2322 ,
" category " : 1
}
] ) ;
let diagnostics : Diagnostics =
serde_json ::from_value ( value ) . expect ( " cannot deserialize " ) ;
assert_eq! ( diagnostics . 0. len ( ) , 4 ) ;
assert! ( diagnostics . 0 [ 0 ] . source_line . is_none ( ) ) ;
assert! ( diagnostics . 0 [ 0 ] . file_name . is_none ( ) ) ;
assert! ( diagnostics . 0 [ 0 ] . start . is_none ( ) ) ;
assert! ( diagnostics . 0 [ 0 ] . end . is_none ( ) ) ;
assert! ( diagnostics . 0 [ 0 ] . message_text . is_some ( ) ) ;
assert! ( diagnostics . 0 [ 0 ] . message_chain . is_none ( ) ) ;
assert! ( diagnostics . 0 [ 0 ] . related_information . is_none ( ) ) ;
assert! ( diagnostics . 0 [ 1 ] . source_line . is_some ( ) ) ;
assert! ( diagnostics . 0 [ 1 ] . file_name . is_some ( ) ) ;
assert! ( diagnostics . 0 [ 1 ] . start . is_some ( ) ) ;
assert! ( diagnostics . 0 [ 1 ] . end . is_some ( ) ) ;
assert! ( diagnostics . 0 [ 1 ] . message_text . is_some ( ) ) ;
assert! ( diagnostics . 0 [ 1 ] . message_chain . is_none ( ) ) ;
assert! ( diagnostics . 0 [ 1 ] . related_information . is_none ( ) ) ;
assert! ( diagnostics . 0 [ 2 ] . source_line . is_some ( ) ) ;
assert! ( diagnostics . 0 [ 2 ] . file_name . is_some ( ) ) ;
assert! ( diagnostics . 0 [ 2 ] . start . is_some ( ) ) ;
assert! ( diagnostics . 0 [ 2 ] . end . is_some ( ) ) ;
assert! ( diagnostics . 0 [ 2 ] . message_text . is_some ( ) ) ;
assert! ( diagnostics . 0 [ 2 ] . message_chain . is_none ( ) ) ;
assert! ( diagnostics . 0 [ 2 ] . related_information . is_some ( ) ) ;
2019-06-04 09:03:56 -04:00
}
#[ test ]
2020-09-12 05:53:57 -04:00
fn test_diagnostics_no_source ( ) {
let value = json! ( [
{
" messageText " : " Unknown compiler option 'invalid'. " ,
" category " :1 ,
" code " :5023
}
] ) ;
let diagnostics : Diagnostics = serde_json ::from_value ( value ) . unwrap ( ) ;
2020-11-09 09:38:29 -05:00
let actual = diagnostics . to_string ( ) ;
2020-09-12 05:53:57 -04:00
assert_eq! (
strip_ansi_codes ( & actual ) ,
" TS5023 [ERROR]: Unknown compiler option \' invalid \' . "
) ;
2019-06-04 09:03:56 -04:00
}
#[ test ]
2020-09-12 05:53:57 -04:00
fn test_diagnostics_basic ( ) {
let value = json! ( [
{
" start " : {
" line " : 0 ,
" character " : 0
} ,
" end " : {
" line " : 0 ,
" character " : 7
} ,
" fileName " : " test.ts " ,
" messageText " : " Cannot find name 'console'. Do you need to change your target library? Try changing the `lib` compiler option to include 'dom'. " ,
" sourceLine " : " console.log( \" a \" ); " ,
" category " : 1 ,
" code " : 2584
}
] ) ;
let diagnostics : Diagnostics = serde_json ::from_value ( value ) . unwrap ( ) ;
2020-11-09 09:38:29 -05:00
let actual = diagnostics . to_string ( ) ;
2020-09-12 05:53:57 -04:00
assert_eq! ( strip_ansi_codes ( & actual ) , " TS2584 [ERROR]: Cannot find name \' console \' . Do you need to change your target library? Try changing the `lib` compiler option to include \' dom \' . \n console.log( \" a \" ); \n ~~~~~~~ \n at test.ts:1:1 " ) ;
2019-06-04 09:03:56 -04:00
}
2020-04-20 15:39:02 -04:00
#[ test ]
2020-09-12 05:53:57 -04:00
fn test_diagnostics_related_info ( ) {
let value = json! ( [
{
" start " : {
" line " : 7 ,
" character " : 0
} ,
" end " : {
" line " : 7 ,
" character " : 7
} ,
" fileName " : " test.ts " ,
" messageText " : " Cannot find name 'foo_Bar'. Did you mean 'foo_bar'? " ,
" sourceLine " : " foo_Bar(); " ,
" relatedInformation " : [
{
" start " : {
" line " : 3 ,
" character " : 9
} ,
" end " : {
" line " : 3 ,
" character " : 16
} ,
" fileName " : " test.ts " ,
" messageText " : " 'foo_bar' is declared here. " ,
" sourceLine " : " function foo_bar() { " ,
" category " : 3 ,
" code " : 2728
}
] ,
" category " : 1 ,
" code " : 2552
}
] ) ;
let diagnostics : Diagnostics = serde_json ::from_value ( value ) . unwrap ( ) ;
2020-11-09 09:38:29 -05:00
let actual = diagnostics . to_string ( ) ;
2020-09-12 05:53:57 -04:00
assert_eq! ( strip_ansi_codes ( & actual ) , " TS2552 [ERROR]: Cannot find name \' foo_Bar \' . Did you mean \' foo_bar \' ? \n foo_Bar(); \n ~~~~~~~ \n at test.ts:8:1 \n \n \' foo_bar \' is declared here. \n function foo_bar() { \n ~~~~~~~ \n at test.ts:4:10 " ) ;
2020-04-20 15:39:02 -04:00
}
2019-06-04 09:03:56 -04:00
}