Skip to content

Commit c9fcc18

Browse files
author
Akos Kitta
committed
Can match multi errors.
Signed-off-by: Akos Kitta <[email protected]>
1 parent a82510b commit c9fcc18

File tree

5 files changed

+127
-150
lines changed

5 files changed

+127
-150
lines changed

arduino-ide-extension/src/browser/contributions/contribution.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import {
5757
} from '../../common/protocol';
5858
import { ArduinoPreferences } from '../arduino-preferences';
5959
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
60+
import { notEmpty } from '@theia/core';
6061

6162
export {
6263
Command,
@@ -214,28 +215,25 @@ export class CoreServiceContribution extends SketchContribution {
214215

215216
private tryHighlightErrorLocation(error: unknown): void {
216217
if (CoreError.is(error)) {
217-
const {
218-
data: { location },
219-
} = error;
220-
if (location) {
221-
const { uri, line, column } = location;
222-
const start = {
223-
line: line - 1,
224-
character: typeof column !== 'number' ? 0 : column - 1,
225-
};
226-
// The double editor activation logic is apparently required: https://github.com/eclipse-theia/theia/issues/11284;
227-
this.editorManager
228-
.getByUri(new URI(uri), { mode: 'activate', selection: { start } })
229-
.then(async (editor) => {
230-
if (editor && this.shell) {
231-
await this.shell.activateWidget(editor.id);
232-
this.markErrorLocationInEditor(editor.editor, {
233-
start,
234-
end: { ...start, character: 1 << 30 },
235-
});
236-
}
237-
});
238-
}
218+
error.data
219+
.map(({ location }) => location)
220+
.filter(notEmpty)
221+
.forEach((location) => {
222+
const { uri, range } = location;
223+
const { start, end } = range;
224+
// The double editor activation logic is apparently required: https://github.com/eclipse-theia/theia/issues/11284;
225+
this.editorManager
226+
.getByUri(new URI(uri), { mode: 'activate', selection: range })
227+
.then(async (editor) => {
228+
if (editor && this.shell) {
229+
await this.shell.activateWidget(editor.id);
230+
this.markErrorLocationInEditor(editor.editor, {
231+
start: start,
232+
end: { ...end, character: 1 << 30 },
233+
});
234+
}
235+
});
236+
});
239237
}
240238
}
241239

arduino-ide-extension/src/common/protocol/core-service.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,19 @@ export namespace CoreError {
2626
Codes.UploadUsingProgrammer
2727
);
2828
export const BurnBootloaderFailed = create(Codes.BurnBootloader);
29-
export function is(error: unknown): error is ApplicationError<number, Info> {
29+
export function is(
30+
error: unknown
31+
): error is ApplicationError<number, Info[]> {
3032
return (
3133
error instanceof Error &&
3234
ApplicationError.is(error) &&
3335
Object.values(Codes).includes(error.code)
3436
);
3537
}
36-
function create(code: number): ApplicationError.Constructor<number, Info> {
38+
function create(code: number): ApplicationError.Constructor<number, Info[]> {
3739
return ApplicationError.declare(
3840
code,
39-
({ message, stack }: Error, data: Info) => {
41+
({ message, stack }: Error, data: Info[]) => {
4042
return {
4143
data,
4244
message,

arduino-ide-extension/src/node/cli-error-parser.ts

Lines changed: 98 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1+
import { notEmpty } from '@theia/core';
12
import { FormatType } from '@theia/core/lib/common/i18n/localization';
23
import { nls } from '@theia/core/lib/common/nls';
34
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';
410
import { Sketch } from '../common/protocol';
511

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-
}
1412
export interface ErrorInfo {
1513
readonly message?: string;
1614
readonly details?: string;
@@ -21,34 +19,19 @@ export interface ErrorSource {
2119
readonly sketch?: Sketch;
2220
}
2321

24-
export function tryParseError(source: ErrorSource): ErrorInfo {
22+
export function tryParseError(source: ErrorSource): ErrorInfo[] {
2523
const { content, sketch } = source;
2624
const err =
2725
typeof content === 'string'
2826
? content
2927
: Buffer.concat(content).toString('utf8');
3028
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());
5033
}
51-
return {};
34+
return [];
5235
}
5336

5437
interface ParseResult {
@@ -59,49 +42,100 @@ interface ParseResult {
5942
readonly error: string;
6043
readonly message?: string;
6144
}
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*((fatal)?\s*error:\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);
6951
}
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)) {
8560
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)}>`
8762
);
63+
return false;
8864
}
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+
);
9085
return {
91-
path,
92-
line,
93-
column,
94-
errorPrefix,
95-
error,
86+
start,
87+
end: start,
9688
};
9789
}
9890

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 {
105139
const knownError = KnownErrors[result.error];
106140
if (!knownError) {
107141
return result;

arduino-ide-extension/src/node/core-service-impl.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
9090
options,
9191
() => new UploadRequest(),
9292
(client, req) => client.upload(req),
93-
(error: Error, info: CoreError.Info) =>
93+
(error: Error, info: CoreError.Info[]) =>
9494
CoreError.UploadFailed(error, info)
9595
);
9696
}
@@ -102,7 +102,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
102102
options,
103103
() => new UploadUsingProgrammerRequest(),
104104
(client, req) => client.uploadUsingProgrammer(req),
105-
(error: Error, info: CoreError.Info) =>
105+
(error: Error, info: CoreError.Info[]) =>
106106
CoreError.UploadUsingProgrammerFailed(error, info)
107107
);
108108
}
@@ -116,8 +116,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
116116
) => ClientReadableStream<UploadResponse | UploadUsingProgrammerResponse>,
117117
errorHandler: (
118118
error: Error,
119-
info: CoreError.Info
120-
) => ApplicationError<number, CoreError.Info>
119+
info: CoreError.Info[]
120+
) => ApplicationError<number, CoreError.Info[]>
121121
): Promise<void> {
122122
await this.compile(Object.assign(options, { exportBinaries: false }));
123123

arduino-ide-extension/src/test/node/cli-error-parser.test.ts

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)