1
+ import { notEmpty } from '@theia/core' ;
1
2
import { FormatType } from '@theia/core/lib/common/i18n/localization' ;
2
3
import { nls } from '@theia/core/lib/common/nls' ;
3
4
import { FileUri } from '@theia/core/lib/node/file-uri' ;
5
+ import {
6
+ Location ,
7
+ Range ,
8
+ Position ,
9
+ } from '@theia/core/shared/vscode-languageserver-protocol' ;
4
10
import { Sketch } from '../common/protocol' ;
5
11
6
- /**
7
- * The location indexing is one-based. Unlike in LSP, the start of the document is `{ line: 1, column: 1 }`.
8
- */
9
- export interface Location {
10
- readonly uri : string ;
11
- readonly line : number ;
12
- readonly column ?: number ;
13
- }
14
12
export interface ErrorInfo {
15
13
readonly message ?: string ;
16
14
readonly details ?: string ;
@@ -21,34 +19,19 @@ export interface ErrorSource {
21
19
readonly sketch ?: Sketch ;
22
20
}
23
21
24
- export function tryParseError ( source : ErrorSource ) : ErrorInfo {
22
+ export function tryParseError ( source : ErrorSource ) : ErrorInfo [ ] {
25
23
const { content, sketch } = source ;
26
24
const err =
27
25
typeof content === 'string'
28
26
? content
29
27
: Buffer . concat ( content ) . toString ( 'utf8' ) ;
30
28
if ( sketch ) {
31
- const result = maybeRemapError ( tryParse ( err ) ) ;
32
- if ( result ) {
33
- const uri = FileUri . create ( result . path ) . toString ( ) ;
34
- if ( ! Sketch . isInSketch ( uri , sketch ) ) {
35
- console . warn (
36
- `URI <${ uri } > is not contained in sketch: <${ JSON . stringify ( sketch ) } >`
37
- ) ;
38
- return { } ;
39
- }
40
- return {
41
- details : result . error ,
42
- message : result . message ,
43
- location : {
44
- uri : FileUri . create ( result . path ) . toString ( ) ,
45
- line : result . line ,
46
- column : result . column ,
47
- } ,
48
- } ;
49
- }
29
+ return tryParse ( err )
30
+ . map ( remapErrorMessages )
31
+ . filter ( isLocationInSketch ( sketch ) )
32
+ . map ( errorInfo ( ) ) ;
50
33
}
51
- return { } ;
34
+ return [ ] ;
52
35
}
53
36
54
37
interface ParseResult {
@@ -59,49 +42,100 @@ interface ParseResult {
59
42
readonly error : string ;
60
43
readonly message ?: string ;
61
44
}
62
- export function tryParse ( raw : string ) : ParseResult | undefined {
63
- // Shamelessly stolen from the Java IDE: https://github.com/arduino/Arduino/blob/43b0818f7fa8073301db1b80ac832b7b7596b828/arduino-core/src/cc/arduino/Compiler.java#L137
64
- const matches =
65
- / ( .+ \. \w + ) : ( \d + ) ( : \d + ) * : \s * ( ( f a t a l ) ? \s * e r r o r : \s * ) ( .* ) \s * / m. exec ( raw ) ;
66
- if ( ! matches ) {
67
- console . warn ( `Could not parse raw error. Skipping.` ) ;
68
- return undefined ;
45
+ namespace ParseResult {
46
+ export function keyOf ( result : ParseResult ) : string {
47
+ /**
48
+ * The CLI compiler might return with the same error multiple times. This is the key function for the distinct set calculation.
49
+ */
50
+ return JSON . stringify ( result ) ;
69
51
}
70
- const [ , path , rawLine , rawColumn , errorPrefix , , error ] = matches . map (
71
- ( match ) => ( match ? match . trim ( ) : match )
72
- ) ;
73
- const line = Number . parseInt ( rawLine , 10 ) ;
74
- if ( ! Number . isInteger ( line ) ) {
75
- console . warn (
76
- `Could not parse line number. Raw input: <${ rawLine } >, parsed integer: <${ line } >.`
77
- ) ;
78
- return undefined ;
79
- }
80
- let column : number | undefined = undefined ;
81
- if ( rawColumn ) {
82
- const normalizedRawColumn = rawColumn . slice ( - 1 ) ; // trims the leading colon
83
- column = Number . parseInt ( normalizedRawColumn , 10 ) ;
84
- if ( ! Number . isInteger ( column ) ) {
52
+ }
53
+
54
+ function isLocationInSketch (
55
+ sketch : Sketch
56
+ ) : ( value : ParseResult , index : number , array : ParseResult [ ] ) => unknown {
57
+ return ( result ) => {
58
+ const uri = FileUri . create ( result . path ) . toString ( ) ;
59
+ if ( ! Sketch . isInSketch ( uri , sketch ) ) {
85
60
console . warn (
86
- `Could not parse column number. Raw input: <${ normalizedRawColumn } >, parsed integer : <${ column } >. `
61
+ `URI <${ uri } > is not contained in sketch : <${ JSON . stringify ( sketch ) } > `
87
62
) ;
63
+ return false ;
88
64
}
89
- }
65
+ return true ;
66
+ } ;
67
+ }
68
+
69
+ function errorInfo ( ) : ( value : ParseResult ) => ErrorInfo {
70
+ return ( { error, message, path, line, column } ) => ( {
71
+ details : error ,
72
+ message,
73
+ location : {
74
+ uri : FileUri . create ( path ) . toString ( ) ,
75
+ range : range ( line , column ) ,
76
+ } ,
77
+ } ) ;
78
+ }
79
+
80
+ function range ( line : number , column ?: number ) : Range {
81
+ const start = Position . create (
82
+ line - 1 ,
83
+ typeof column === 'number' ? column - 1 : 0
84
+ ) ;
90
85
return {
91
- path,
92
- line,
93
- column,
94
- errorPrefix,
95
- error,
86
+ start,
87
+ end : start ,
96
88
} ;
97
89
}
98
90
99
- function maybeRemapError (
100
- result : ParseResult | undefined
101
- ) : ParseResult | undefined {
102
- if ( ! result ) {
103
- return undefined ;
104
- }
91
+ export function tryParse ( raw : string ) : ParseResult [ ] {
92
+ // Shamelessly stolen from the Java IDE: https://github.com/arduino/Arduino/blob/43b0818f7fa8073301db1b80ac832b7b7596b828/arduino-core/src/cc/arduino/Compiler.java#L137
93
+ const re = new RegExp (
94
+ '(.+\\.\\w+):(\\d+)(:\\d+)*:\\s*((fatal)?\\s*error:\\s*)(.*)\\s*' ,
95
+ 'gm'
96
+ ) ;
97
+ return [
98
+ ...new Map (
99
+ Array . from ( raw . matchAll ( re ) ?? [ ] )
100
+ . map ( ( match ) => {
101
+ const [ , path , rawLine , rawColumn , errorPrefix , , error ] = match . map (
102
+ ( match ) => ( match ? match . trim ( ) : match )
103
+ ) ;
104
+ const line = Number . parseInt ( rawLine , 10 ) ;
105
+ if ( ! Number . isInteger ( line ) ) {
106
+ console . warn (
107
+ `Could not parse line number. Raw input: <${ rawLine } >, parsed integer: <${ line } >.`
108
+ ) ;
109
+ return undefined ;
110
+ }
111
+ let column : number | undefined = undefined ;
112
+ if ( rawColumn ) {
113
+ const normalizedRawColumn = rawColumn . slice ( - 1 ) ; // trims the leading colon => `:3` will be `3`
114
+ column = Number . parseInt ( normalizedRawColumn , 10 ) ;
115
+ if ( ! Number . isInteger ( column ) ) {
116
+ console . warn (
117
+ `Could not parse column number. Raw input: <${ normalizedRawColumn } >, parsed integer: <${ column } >.`
118
+ ) ;
119
+ }
120
+ }
121
+ return {
122
+ path,
123
+ line,
124
+ column,
125
+ errorPrefix,
126
+ error,
127
+ } ;
128
+ } )
129
+ . filter ( notEmpty )
130
+ . map ( ( result ) => [ ParseResult . keyOf ( result ) , result ] )
131
+ ) . values ( ) ,
132
+ ] ;
133
+ }
134
+
135
+ /**
136
+ * Converts cryptic and legacy error messages to nice ones. Taken from the Java IDE.
137
+ */
138
+ function remapErrorMessages ( result : ParseResult ) : ParseResult {
105
139
const knownError = KnownErrors [ result . error ] ;
106
140
if ( ! knownError ) {
107
141
return result ;
0 commit comments