2024-01-01 14:58:21 -05:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2020-02-24 14:48:14 -05:00
2024-01-10 17:40:30 -05:00
use deno_ast ::ModuleSpecifier ;
use deno_graph ::ModuleGraph ;
2024-02-07 11:25:14 -05:00
use deno_terminal ::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 ;
2024-01-10 17:40:30 -05:00
use deno_core ::sourcemap ::SourceMap ;
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 ;
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 {
2024-01-10 17:40:30 -05:00
/// 0-indexed line number
2020-09-12 05:53:57 -04:00
pub line : u64 ,
2024-01-10 17:40:30 -05:00
/// 0-indexed character number
2020-09-12 05:53:57 -04:00
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 > ,
2024-01-10 17:40:30 -05:00
/// Position of this diagnostic in the original non-mapped source.
///
/// This will exist and be different from the `start` for fast
/// checked modules where the TypeScript source will differ
/// from the original source.
#[ serde(skip_serializing) ]
pub original_source_start : Option < Position > ,
2020-10-13 19:52:49 -04:00
pub message_text : Option < String > ,
2023-10-02 02:32:05 -04:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-10-13 19:52:49 -04:00
pub message_chain : Option < DiagnosticMessageChain > ,
2023-10-02 02:32:05 -04:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-10-13 19:52:49 -04:00
pub source : Option < String > ,
pub source_line : Option < String > ,
pub file_name : Option < String > ,
2023-10-02 02:32:05 -04:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-10-13 19:52:49 -04:00
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 {
2024-03-31 16:39:40 -04:00
/// If this diagnostic should be included when it comes from a remote module.
pub fn include_when_remote ( & self ) -> bool {
/// TS6133: value is declared but its value is never read (noUnusedParameters and noUnusedLocals)
const TS6133 : u64 = 6133 ;
self . code ! = TS6133
}
2020-09-12 05:53:57 -04:00
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 {
2024-01-10 17:40:30 -05:00
if let ( Some ( file_name ) , Some ( start ) ) = (
self . file_name . as_ref ( ) ,
self . original_source_start . as_ref ( ) . or ( self . start . as_ref ( ) ) ,
) {
2020-09-12 05:53:57 -04:00
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$}{} " ,
" " ,
2023-07-13 19:29:51 -04:00
self . message_text . as_deref ( ) . unwrap_or_default ( ) ,
2020-09-12 05:53:57 -04:00
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.
2024-03-31 16:39:40 -04:00
pub fn filter < P > ( self , predicate : P ) -> Self
2021-11-29 17:23:30 -05:00
where
2024-03-31 16:39:40 -04:00
P : FnMut ( & Diagnostic ) -> bool ,
2021-11-29 17:23:30 -05:00
{
2024-03-31 16:39:40 -04:00
let diagnostics = self . 0. into_iter ( ) . filter ( 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 ( )
}
2024-01-10 17:40:30 -05:00
/// Modifies all the diagnostics to have their display positions
/// modified to point at the original source.
pub fn apply_fast_check_source_maps ( & mut self , graph : & ModuleGraph ) {
fn visit_diagnostic ( d : & mut Diagnostic , graph : & ModuleGraph ) {
if let Some ( specifier ) = d
. file_name
. as_ref ( )
. and_then ( | n | ModuleSpecifier ::parse ( n ) . ok ( ) )
{
if let Ok ( Some ( module ) ) = graph . try_get_prefer_types ( & specifier ) {
if let Some ( fast_check_module ) =
2024-01-31 22:15:22 -05:00
module . js ( ) . and_then ( | m | m . fast_check_module ( ) )
2024-01-10 17:40:30 -05:00
{
// todo(dsherret): use a short lived cache to prevent parsing
// source maps so often
if let Ok ( source_map ) =
SourceMap ::from_slice ( & fast_check_module . source_map )
{
if let Some ( start ) = d . start . as_mut ( ) {
let maybe_token = source_map
. lookup_token ( start . line as u32 , start . character as u32 ) ;
if let Some ( token ) = maybe_token {
d . original_source_start = Some ( Position {
line : token . get_src_line ( ) as u64 ,
character : token . get_src_col ( ) as u64 ,
} ) ;
}
}
}
}
}
}
if let Some ( related ) = & mut d . related_information {
for d in related . iter_mut ( ) {
visit_diagnostic ( d , graph ) ;
}
}
}
for d in & mut self . 0 {
visit_diagnostic ( d , graph ) ;
}
}
2020-10-27 20:52:20 -04:00
}
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
}