5
5
extern crate napi_derive;
6
6
7
7
use std:: env;
8
- use std:: fmt;
9
8
use std:: fs;
10
- use std:: io:: Write ;
11
9
use std:: str;
12
10
11
+ use annotate_snippets:: { display_list, snippet} ;
12
+ use ast_view:: { SourceFile , SourceFileTextInfo } ;
13
13
use deno_lint:: ast_parser:: get_default_ts_config;
14
- use deno_lint:: diagnostic:: LintDiagnostic ;
14
+ use deno_lint:: diagnostic:: { LintDiagnostic , Range } ;
15
15
use deno_lint:: linter:: LinterBuilder ;
16
16
use deno_lint:: rules:: { get_all_rules, get_recommended_rules} ;
17
17
use ignore:: types:: TypesBuilder ;
18
18
use ignore:: WalkBuilder ;
19
19
use napi:: { CallContext , Error , JsBoolean , JsBuffer , JsObject , JsString , Result , Status } ;
20
20
use swc_ecmascript:: parser:: Syntax ;
21
21
use swc_ecmascript:: parser:: TsConfig ;
22
- use termcolor:: Color :: { Ansi256 , Red } ;
23
- use termcolor:: { Ansi , ColorSpec , WriteColor } ;
24
-
25
- #[ cfg( windows) ]
26
- use termcolor:: { BufferWriter , ColorChoice } ;
27
22
28
23
#[ cfg( all(
29
24
target_arch = "x86_64" ,
@@ -33,150 +28,60 @@ use termcolor::{BufferWriter, ColorChoice};
33
28
#[ global_allocator]
34
29
static ALLOC : mimalloc:: MiMalloc = mimalloc:: MiMalloc ;
35
30
36
- #[ allow( unused) ]
37
- #[ cfg( windows) ]
38
- fn enable_ansi ( ) {
39
- BufferWriter :: stdout ( ColorChoice :: AlwaysAnsi ) ;
40
- }
41
-
42
- fn gray ( s : String ) -> impl fmt:: Display {
43
- let mut style_spec = ColorSpec :: new ( ) ;
44
- style_spec. set_fg ( Some ( Ansi256 ( 8 ) ) ) ;
45
- style ( & s, style_spec)
46
- }
47
-
48
- fn red ( s : String ) -> impl fmt:: Display {
49
- let mut style_spec = ColorSpec :: new ( ) ;
50
- style_spec. set_fg ( Some ( Red ) ) ;
51
- style ( & s, style_spec)
52
- }
53
-
54
- fn cyan ( s : String ) -> impl fmt:: Display {
55
- let mut style_spec = ColorSpec :: new ( ) ;
56
- style_spec. set_fg ( Some ( Ansi256 ( 14 ) ) ) ;
57
- style ( & s, style_spec)
58
- }
59
-
60
- fn bold ( s : String ) -> impl fmt:: Display {
61
- let mut style_spec = ColorSpec :: new ( ) ;
62
- style_spec. set_bold ( true ) ;
63
- style ( & s, style_spec)
64
- }
65
-
66
- fn style ( s : & str , colorspec : ColorSpec ) -> impl fmt:: Display {
67
- let mut v = Vec :: new ( ) ;
68
- let mut ansi_writer = Ansi :: new ( & mut v) ;
69
- ansi_writer. set_color ( & colorspec) . unwrap ( ) ;
70
- ansi_writer. write_all ( s. as_bytes ( ) ) . unwrap ( ) ;
71
- ansi_writer. reset ( ) . unwrap ( ) ;
72
- String :: from_utf8_lossy ( & v) . into_owned ( )
31
+ // Return slice of source code covered by diagnostic
32
+ // and adjusted range of diagnostic (ie. original range - start line
33
+ // of sliced source code).
34
+ fn get_slice_source_and_range < ' a > (
35
+ source_file : & ' a SourceFileTextInfo ,
36
+ range : & Range ,
37
+ ) -> ( & ' a str , ( usize , usize ) ) {
38
+ let first_line_start = source_file. line_start ( range. start . line_index ) . 0 as usize ;
39
+ let last_line_end = source_file. line_end ( range. end . line_index ) . 0 as usize ;
40
+ let adjusted_start = range. start . byte_pos - first_line_start;
41
+ let adjusted_end = range. end . byte_pos - first_line_start;
42
+ let adjusted_range = ( adjusted_start, adjusted_end) ;
43
+ let slice_str = & source_file. text ( ) [ first_line_start..last_line_end] ;
44
+ ( slice_str, adjusted_range)
73
45
}
74
46
75
- pub fn format_diagnostic ( diagnostic : & LintDiagnostic , source : & str ) -> String {
76
- let pretty_error = format ! (
77
- "({}) {}" ,
78
- gray( diagnostic. code. to_string( ) ) ,
79
- diagnostic. message
80
- ) ;
81
-
82
- let file_name = & diagnostic. filename ;
83
- let location =
84
- if file_name. contains ( '/' ) || file_name. contains ( '\\' ) || file_name. starts_with ( "./" ) {
85
- file_name. to_string ( )
86
- } else {
87
- format ! ( "./{}" , file_name)
88
- } ;
89
-
90
- let line_str_len = diagnostic. range . end . line . to_string ( ) . len ( ) ;
91
- let pretty_location = cyan ( format ! (
92
- "{}--> {}:{}:{}" ,
93
- " " . repeat( line_str_len) ,
94
- location,
95
- diagnostic. range. start. line,
96
- diagnostic. range. start. col
97
- ) )
98
- . to_string ( ) ;
99
-
100
- let dummy = format ! ( "{} |" , " " . repeat( line_str_len) ) ;
101
-
102
- if diagnostic. range . start . line == diagnostic. range . end . line {
103
- let snippet_length = diagnostic. range . end . col - diagnostic. range . start . col ;
104
- let source_lines: Vec < & str > = source. split ( '\n' ) . collect ( ) ;
105
- let line = source_lines[ diagnostic. range . start . line - 1 ] ;
106
- let pretty_line_src = format ! ( "{} | {}" , diagnostic. range. start. line, line) ;
107
- let red_glyphs = format ! (
108
- "{} | {}{}" ,
109
- " " . repeat( line_str_len) ,
110
- " " . repeat( diagnostic. range. start. col) ,
111
- red( "^" . repeat( snippet_length) )
112
- ) ;
113
-
114
- let lines = vec ! [
115
- pretty_error,
116
- pretty_location,
117
- dummy. clone( ) ,
118
- pretty_line_src,
119
- red_glyphs,
120
- dummy,
121
- ] ;
122
-
123
- lines. join ( "\n " )
47
+ fn format_diagnostic ( diagnostic : & LintDiagnostic , source_file : & SourceFileTextInfo ) -> String {
48
+ let ( slice_source, range) = get_slice_source_and_range ( source_file, & diagnostic. range ) ;
49
+ let footer = if let Some ( hint) = & diagnostic. hint {
50
+ vec ! [ snippet:: Annotation {
51
+ label: Some ( hint) ,
52
+ id: None ,
53
+ annotation_type: snippet:: AnnotationType :: Help ,
54
+ } ]
124
55
} else {
125
- let mut lines = vec ! [ pretty_error, pretty_location, dummy. clone( ) ] ;
126
- let source_lines: Vec < & str > = source. split ( '\n' ) . collect ( ) ;
127
-
128
- for i in diagnostic. range . start . line ..( diagnostic. range . end . line + 1 ) {
129
- let line = source_lines[ i - 1 ] ;
130
- let is_first = i == diagnostic. range . start . line ;
131
- let is_last = i == diagnostic. range . end . line ;
132
-
133
- if is_first {
134
- let ( rest, snippet) = line. split_at ( diagnostic. range . start . col ) ;
135
- lines. push ( format ! ( "{} | {}{}" , i, rest, bold( snippet. to_string( ) ) ) ) ;
136
- } else if is_last {
137
- let ( snippet, rest) = line. split_at ( diagnostic. range . end . col ) ;
138
- lines. push ( format ! (
139
- "{} | {} {}{}" ,
140
- i,
141
- red( "|" . to_string( ) ) ,
142
- bold( snippet. to_string( ) ) ,
143
- rest
144
- ) ) ;
145
- } else {
146
- lines. push ( format ! (
147
- "{} | {} {}" ,
148
- i,
149
- red( "|" . to_string( ) ) ,
150
- bold( line. to_string( ) )
151
- ) ) ;
152
- }
153
-
154
- // If this is the first line, render the ∨ symbols
155
- if is_first {
156
- lines. push ( format ! (
157
- "{} | {}{}" ,
158
- " " . repeat( line_str_len) ,
159
- red( "_" . repeat( diagnostic. range. start. col + 1 ) ) ,
160
- red( "^" . to_string( ) )
161
- ) ) ;
162
- }
163
-
164
- // If this is the last line, render the ∨ symbols
165
- if is_last {
166
- lines. push ( format ! (
167
- "{} | {}{}{}" ,
168
- " " . repeat( line_str_len) ,
169
- red( "|" . to_string( ) ) ,
170
- red( "_" . repeat( diagnostic. range. end. col) ) ,
171
- red( "^" . to_string( ) )
172
- ) ) ;
173
- }
174
- }
175
-
176
- lines. push ( dummy) ;
56
+ vec ! [ ]
57
+ } ;
177
58
178
- lines. join ( "\n " )
179
- }
59
+ let snippet = snippet:: Snippet {
60
+ title : Some ( snippet:: Annotation {
61
+ label : Some ( & diagnostic. message ) ,
62
+ id : Some ( & diagnostic. code ) ,
63
+ annotation_type : snippet:: AnnotationType :: Error ,
64
+ } ) ,
65
+ footer,
66
+ slices : vec ! [ snippet:: Slice {
67
+ source: slice_source,
68
+ line_start: diagnostic. range. start. line_index + 1 , // make 1-indexed
69
+ origin: Some ( & diagnostic. filename) ,
70
+ fold: false ,
71
+ annotations: vec![ snippet:: SourceAnnotation {
72
+ range,
73
+ label: "" ,
74
+ annotation_type: snippet:: AnnotationType :: Error ,
75
+ } ] ,
76
+ } ] ,
77
+ opt : display_list:: FormatOptions {
78
+ color : true ,
79
+ anonymized_line_numbers : false ,
80
+ margin : None ,
81
+ } ,
82
+ } ;
83
+ let display_list = display_list:: DisplayList :: from ( snippet) ;
84
+ format ! ( "{}" , display_list)
180
85
}
181
86
182
87
#[ module_exports]
@@ -208,7 +113,7 @@ fn lint(ctx: CallContext) -> Result<JsObject> {
208
113
209
114
let file_name_ref = file_name. as_str ( ) ?;
210
115
211
- let ( _ , file_diagnostics) = linter
116
+ let ( s , file_diagnostics) = linter
212
117
. lint ( file_name_ref. to_owned ( ) , source_string. to_owned ( ) )
213
118
. map_err ( |e| Error {
214
119
status : Status :: GenericFailure ,
@@ -222,7 +127,7 @@ fn lint(ctx: CallContext) -> Result<JsObject> {
222
127
index as _ ,
223
128
ctx
224
129
. env
225
- . create_string ( format_diagnostic ( diagnostic, source_string ) . as_str ( ) ) ?,
130
+ . create_string ( format_diagnostic ( & diagnostic, & s ) . as_str ( ) ) ?,
226
131
) ?;
227
132
}
228
133
@@ -314,7 +219,7 @@ fn lint_command(ctx: CallContext) -> Result<JsBoolean> {
314
219
} )
315
220
. syntax ( syntax)
316
221
. build ( ) ;
317
- let ( _ , file_diagnostics) = linter
222
+ let ( s , file_diagnostics) = linter
318
223
. lint (
319
224
( & p. to_str ( ) )
320
225
. ok_or ( Error :: from_reason ( format ! (
@@ -330,7 +235,7 @@ fn lint_command(ctx: CallContext) -> Result<JsBoolean> {
330
235
} ) ?;
331
236
for diagnostic in file_diagnostics {
332
237
has_error = true ;
333
- println ! ( "{}" , format_diagnostic( & diagnostic, file_content . as_str ( ) ) ) ;
238
+ println ! ( "{}" , format_diagnostic( & diagnostic, & s ) ) ;
334
239
}
335
240
}
336
241
}
0 commit comments