@@ -5,25 +5,41 @@ import {
55 Range ,
66 Position ,
77} from '@theia/core/shared/vscode-languageserver-protocol' ;
8- import type { CoreError } from '../common/protocol' ;
8+ import { CoreError } from '../common/protocol' ;
99import { Sketch } from '../common/protocol/sketches-service' ;
1010
11- export interface ErrorSource {
11+ export interface OutputSource {
1212 readonly content : string | ReadonlyArray < Uint8Array > ;
1313 readonly sketch ?: Sketch ;
1414}
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'
2019 ? content
2120 : 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 ) ;
2227 if ( sketch ) {
23- return tryParse ( err )
28+ return tryParse ( content )
2429 . map ( remapErrorMessages )
2530 . 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 [ ] ) ;
2743 }
2844 return [ ] ;
2945}
@@ -35,6 +51,7 @@ interface ParseResult {
3551 readonly errorPrefix : string ;
3652 readonly error : string ;
3753 readonly message ?: string ;
54+ readonly rangeInOutput ?: Range | undefined ;
3855}
3956namespace ParseResult {
4057 export function keyOf ( result : ParseResult ) : string {
@@ -64,6 +81,7 @@ function toErrorInfo({
6481 path,
6582 line,
6683 column,
84+ rangeInOutput,
6785} : ParseResult ) : CoreError . ErrorLocation {
6886 return {
6987 message : error ,
@@ -72,6 +90,7 @@ function toErrorInfo({
7290 uri : FileUri . create ( path ) . toString ( ) ,
7391 range : range ( line , column ) ,
7492 } ,
93+ rangesInOutput : rangeInOutput ? [ rangeInOutput ] : [ ] ,
7594 } ;
7695}
7796
@@ -86,48 +105,50 @@ function range(line: number, column?: number): Range {
86105 } ;
87106}
88107
89- export function tryParse ( raw : string ) : ParseResult [ ] {
108+ function tryParse ( content : string ) : ParseResult [ ] {
90109 // Shamelessly stolen from the Java IDE: https://github.com/arduino/Arduino/blob/43b0818f7fa8073301db1b80ac832b7b7596b828/arduino-core/src/cc/arduino/Compiler.java#L137
91110 const re = new RegExp (
92111 '(.+\\.\\w+):(\\d+)(:\\d+)*:\\s*((fatal)?\\s*error:\\s*)(.*)\\s*' ,
93112 'gm'
94113 ) ;
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 } >.`
101134 ) ;
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 ) ;
131152}
132153
133154/**
@@ -161,3 +182,47 @@ const KnownErrors: Record<string, { error: string; message?: string }> = {
161182 ) ,
162183 } ,
163184} ;
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