@@ -5,25 +5,41 @@ import {
5
5
Range ,
6
6
Position ,
7
7
} from '@theia/core/shared/vscode-languageserver-protocol' ;
8
- import type { CoreError } from '../common/protocol' ;
8
+ import { CoreError } from '../common/protocol' ;
9
9
import { Sketch } from '../common/protocol/sketches-service' ;
10
10
11
- export interface ErrorSource {
11
+ export interface OutputSource {
12
12
readonly content : string | ReadonlyArray < Uint8Array > ;
13
13
readonly sketch ?: Sketch ;
14
14
}
15
-
16
- export function tryParseError ( source : ErrorSource ) : CoreError . ErrorLocation [ ] {
17
- const { content, sketch } = source ;
18
- const err =
19
- typeof content === 'string'
15
+ export namespace OutputSource {
16
+ export function content ( source : OutputSource ) : string {
17
+ const { content } = source ;
18
+ return typeof content === 'string'
20
19
? content
21
20
: Buffer . concat ( content ) . toString ( 'utf8' ) ;
21
+ }
22
+ }
23
+
24
+ export function tryParseError ( source : OutputSource ) : CoreError . ErrorLocation [ ] {
25
+ const { sketch } = source ;
26
+ const content = OutputSource . content ( source ) ;
22
27
if ( sketch ) {
23
- return tryParse ( err )
28
+ return tryParse ( content )
24
29
. map ( remapErrorMessages )
25
30
. filter ( isLocationInSketch ( sketch ) )
26
- . map ( toErrorInfo ) ;
31
+ . map ( toErrorInfo )
32
+ . reduce ( ( acc , curr ) => {
33
+ const existingRef = acc . find ( ( candidate ) =>
34
+ CoreError . ErrorLocationRef . equals ( candidate , curr )
35
+ ) ;
36
+ if ( existingRef ) {
37
+ existingRef . rangesInOutput . push ( ...curr . rangesInOutput ) ;
38
+ } else {
39
+ acc . push ( curr ) ;
40
+ }
41
+ return acc ;
42
+ } , [ ] as CoreError . ErrorLocation [ ] ) ;
27
43
}
28
44
return [ ] ;
29
45
}
@@ -35,6 +51,7 @@ interface ParseResult {
35
51
readonly errorPrefix : string ;
36
52
readonly error : string ;
37
53
readonly message ?: string ;
54
+ readonly rangeInOutput ?: Range | undefined ;
38
55
}
39
56
namespace ParseResult {
40
57
export function keyOf ( result : ParseResult ) : string {
@@ -64,6 +81,7 @@ function toErrorInfo({
64
81
path,
65
82
line,
66
83
column,
84
+ rangeInOutput,
67
85
} : ParseResult ) : CoreError . ErrorLocation {
68
86
return {
69
87
message : error ,
@@ -72,6 +90,7 @@ function toErrorInfo({
72
90
uri : FileUri . create ( path ) . toString ( ) ,
73
91
range : range ( line , column ) ,
74
92
} ,
93
+ rangesInOutput : rangeInOutput ? [ rangeInOutput ] : [ ] ,
75
94
} ;
76
95
}
77
96
@@ -86,48 +105,50 @@ function range(line: number, column?: number): Range {
86
105
} ;
87
106
}
88
107
89
- export function tryParse ( raw : string ) : ParseResult [ ] {
108
+ function tryParse ( content : string ) : ParseResult [ ] {
90
109
// Shamelessly stolen from the Java IDE: https://github.com/arduino/Arduino/blob/43b0818f7fa8073301db1b80ac832b7b7596b828/arduino-core/src/cc/arduino/Compiler.java#L137
91
110
const re = new RegExp (
92
111
'(.+\\.\\w+):(\\d+)(:\\d+)*:\\s*((fatal)?\\s*error:\\s*)(.*)\\s*' ,
93
112
'gm'
94
113
) ;
95
- return [
96
- ...new Map (
97
- Array . from ( raw . matchAll ( re ) ?? [ ] )
98
- . map ( ( match ) => {
99
- const [ , path , rawLine , rawColumn , errorPrefix , , error ] = match . map (
100
- ( match ) => ( match ? match . trim ( ) : match )
114
+ return Array . from ( content . matchAll ( re ) ?? [ ] )
115
+ . map ( ( match ) => {
116
+ const { index : start } = match ;
117
+ const [ , path , rawLine , rawColumn , errorPrefix , , error ] = match . map (
118
+ ( match ) => ( match ? match . trim ( ) : match )
119
+ ) ;
120
+ const line = Number . parseInt ( rawLine , 10 ) ;
121
+ if ( ! Number . isInteger ( line ) ) {
122
+ console . warn (
123
+ `Could not parse line number. Raw input: <${ rawLine } >, parsed integer: <${ line } >.`
124
+ ) ;
125
+ return undefined ;
126
+ }
127
+ let column : number | undefined = undefined ;
128
+ if ( rawColumn ) {
129
+ const normalizedRawColumn = rawColumn . slice ( - 1 ) ; // trims the leading colon => `:3` will be `3`
130
+ column = Number . parseInt ( normalizedRawColumn , 10 ) ;
131
+ if ( ! Number . isInteger ( column ) ) {
132
+ console . warn (
133
+ `Could not parse column number. Raw input: <${ normalizedRawColumn } >, parsed integer: <${ column } >.`
101
134
) ;
102
- const line = Number . parseInt ( rawLine , 10 ) ;
103
- if ( ! Number . isInteger ( line ) ) {
104
- console . warn (
105
- `Could not parse line number. Raw input: <${ rawLine } >, parsed integer: <${ line } >.`
106
- ) ;
107
- return undefined ;
108
- }
109
- let column : number | undefined = undefined ;
110
- if ( rawColumn ) {
111
- const normalizedRawColumn = rawColumn . slice ( - 1 ) ; // trims the leading colon => `:3` will be `3`
112
- column = Number . parseInt ( normalizedRawColumn , 10 ) ;
113
- if ( ! Number . isInteger ( column ) ) {
114
- console . warn (
115
- `Could not parse column number. Raw input: <${ normalizedRawColumn } >, parsed integer: <${ column } >.`
116
- ) ;
117
- }
118
- }
119
- return {
120
- path,
121
- line,
122
- column,
123
- errorPrefix,
124
- error,
125
- } ;
126
- } )
127
- . filter ( notEmpty )
128
- . map ( ( result ) => [ ParseResult . keyOf ( result ) , result ] )
129
- ) . values ( ) ,
130
- ] ;
135
+ }
136
+ }
137
+ const rangeInOutput = findRangeInOutput (
138
+ start ,
139
+ { path, rawLine, rawColumn } ,
140
+ content
141
+ ) ;
142
+ return {
143
+ path,
144
+ line,
145
+ column,
146
+ errorPrefix,
147
+ error,
148
+ rangeInOutput,
149
+ } ;
150
+ } )
151
+ . filter ( notEmpty ) ;
131
152
}
132
153
133
154
/**
@@ -161,3 +182,47 @@ const KnownErrors: Record<string, { error: string; message?: string }> = {
161
182
) ,
162
183
} ,
163
184
} ;
185
+
186
+ function findRangeInOutput (
187
+ startIndex : number | undefined ,
188
+ groups : { path : string ; rawLine : string ; rawColumn : string | null } ,
189
+ content : string // TODO? lines: string[]? can this code break line on `\n`? const lines = content.split(/\r?\n/) ?? [];
190
+ ) : Range | undefined {
191
+ if ( startIndex === undefined ) {
192
+ return undefined ;
193
+ }
194
+ // /path/to/location/Sketch/Sketch.ino:36:42
195
+ const offset =
196
+ groups . path . length +
197
+ ':' . length +
198
+ groups . rawLine . length +
199
+ ( groups . rawColumn ? groups . rawColumn . length : 0 ) ;
200
+ const start = toPosition ( startIndex , content ) ;
201
+ if ( ! start ) {
202
+ return undefined ;
203
+ }
204
+ const end = toPosition ( startIndex + offset , content ) ;
205
+ if ( ! end ) {
206
+ return undefined ;
207
+ }
208
+ return { start, end } ;
209
+ }
210
+
211
+ function toPosition ( offset : number , content : string ) : Position | undefined {
212
+ let line = 0 ;
213
+ let character = 0 ;
214
+ const length = content . length ;
215
+ for ( let i = 0 ; i < length ; i ++ ) {
216
+ const c = content . charAt ( i ) ;
217
+ if ( i === offset ) {
218
+ return { line, character } ;
219
+ }
220
+ if ( c === '\n' ) {
221
+ line ++ ;
222
+ character = 0 ;
223
+ } else {
224
+ character ++ ;
225
+ }
226
+ }
227
+ return undefined ;
228
+ }
0 commit comments