From 3b50312428ecf081b1a51d4539a5248c324d1c51 Mon Sep 17 00:00:00 2001 From: Andrew Lygin Date: Sat, 17 Sep 2016 22:23:48 +0300 Subject: [PATCH 1/8] Improved support for JSON error format --- lib/cargo.js | 222 +++++---------------------------------- lib/errors.js | 33 ++++++ lib/json-parser.js | 250 ++++++++++++++++++++++++++++++++++++++++++++ lib/panic-parser.js | 112 ++++++++++++++++++++ styles/linter.less | 12 +++ 5 files changed, 436 insertions(+), 193 deletions(-) create mode 100644 lib/errors.js create mode 100644 lib/json-parser.js create mode 100644 lib/panic-parser.js diff --git a/lib/cargo.js b/lib/cargo.js index 51a9e46..91edee2 100644 --- a/lib/cargo.js +++ b/lib/cargo.js @@ -47,10 +47,10 @@ export const config = { order: 4 }, jsonErrors: { - title: 'Use json errors format', - description: 'Instead of using regex to parse the human readable output (requires rustc version 1.7)\nNote: this is an unstable feature of the Rust compiler and prone to change and break frequently.', + title: 'Use JSON error format', + description: 'Use JSON error format instead of human readable output. When switched off, Linter is not used to display compiler messages.', type: 'boolean', - default: false, + default: true, order: 5 }, openDocs: { @@ -98,181 +98,13 @@ export function provideBuilder() { settings() { const path = require('path'); - - // Constants to detect links to Rust's source code and make them followable - const unixRustSrcPrefix = '../src/'; - const windowsRustSrcPrefix = '..\\src\\'; + const err = require('./errors'); + const jsonParser = require('./json-parser'); + const panicParser = require('./panic-parser') let buildWorkDir; // The last build workding directory (might differ from the project root for multi-crate projects) - let panicsCounter = 0; // Counts all panics const panicsLimit = 10; // Max number of panics to show at once - function level2severity(level) { - switch (level) { - case 'warning': return 'warning'; - case 'error': return 'error'; - case 'note': return 'info'; - case 'help': return 'info'; - default: return 'error'; - } - } - - function level2type(level) { - return level.charAt(0).toUpperCase() + level.slice(1); - } - - // Checks if the given file path returned by rustc or cargo points to the Rust source code - function isRustSourceLink(filePath) { - return filePath.startsWith(unixRustSrcPrefix) || filePath.startsWith(windowsRustSrcPrefix); - } - - function parseJsonSpan(span, msg) { - if (span && span.file_name && !span.file_name.startsWith('<')) { - msg.file = span.file_name; - msg.line = span.line_start; - msg.line_end = span.line_end; - msg.col = span.column_start; - msg.col_end = span.column_end; - return true; - } else if (span.expansion) { - return parseJsonSpan(span.expansion.span, msg); - } - return false; - } - - function parseJsonSpans(jsonObj, msg) { - if (jsonObj.spans) { - jsonObj.spans.forEach(span => { - if (parseJsonSpan(span, msg)) { - return; - } - }); - } - } - - // Parses a compile message in json format - function parseJsonMessage(line, messages) { - const json = JSON.parse(line); - const msg = { - message: json.message, - type: level2type(json.level), - severity: level2severity(json.level), - trace: [] - }; - parseJsonSpans(json, msg); - json.children.forEach(child => { - const tr = { - message: child.message, - type: level2type(child.level), - severity: level2severity(child.level) - }; - parseJsonSpans(child, tr); - msg.trace.push(tr); - }); - if (json.code && json.code.explanation) { - msg.trace.push({ - message: json.code.explanation, - type: 'Explanation', - severity: 'info' - }); - } - if (msg.file) { // Root message without `file` is the summary, skip it - messages.push(msg); - } - } - - // Shows panic info - function showPanic(panic) { - // Only add link if we have panic.filePath, otherwise it's an external link - atom.notifications.addError( - 'A thread panicked at ' - + (panic.filePath ? '' : '') - + 'line ' + panic.line + ' in ' + panic.file - + (panic.filePath ? '' : ''), { - detail: panic.message, - stack: panic.stack, - dismissable: true - }); - if (panic.filePath) { - const link = document.getElementById(panic.id); - if (link) { - link.panic = panic; - link.addEventListener('click', function (e) { - atom.workspace.open(e.target.panic.filePath, { - searchAllPanes: true, - initialLine: e.target.panic.line - 1 - }); - }); - } - } - } - - // Tries to parse a stack trace. Returns the quantity of actually parsed lines. - function tryParseStackTrace(lines, i, panic) { - let parsedQty = 0; - let line = lines[i]; - if (line.substring(0, 16) === 'stack backtrace:') { - parsedQty += 1; - const panicLines = []; - for (let j = i + 1; j < lines.length; j++) { - line = lines[j]; - const matchFunc = /^(\s+\d+):\s+0x[a-f0-9]+ - (?:(.+)::h[0-9a-f]+|(.+))$/g.exec(line); - if (matchFunc) { - // A line with a function call - if (atom.config.get('build-cargo.backtraceType') === 'Compact') { - line = matchFunc[1] + ': ' + (matchFunc[2] || matchFunc[3]); - } - panicLines.push(line); - } else { - const matchLink = /(at (.+):(\d+))$/g.exec(line); - if (matchLink) { - // A line with a file link - if (!panic.file && !isRustSourceLink(matchLink[2])) { - panic.file = matchLink[2]; // Found a link to our source code - panic.line = matchLink[3]; - } - panicLines.push(' ' + matchLink[1]); // less leading spaces - } else { - // Stack trace has ended - break; - } - } - parsedQty += 1; - } - panic.stack = panicLines.join('\n'); - } - return parsedQty; - } - - // Tries to parse a panic and its stack trace. Returns the quantity of actually - // parsed lines. - function tryParsePanic(lines, i, show) { - const line = lines[i]; - const match = /(thread '.+' panicked at '.+'), ([^\/][^\:]+):(\d+)/g.exec(line); - let parsedQty = 0; - if (match) { - parsedQty = 1; - const panic = { - id: 'build-cargo-panic-' + (++panicsCounter), // Unique panic ID - message: match[1], - file: isRustSourceLink(match[2]) ? undefined : match[2], - filePath: undefined, - line: parseInt(match[3], 10), - stack: undefined - }; - parsedQty = 1 + tryParseStackTrace(lines, i + 1, panic); - if (panic.file) { - panic.filePath = path.isAbsolute(panic.file) ? panic.file : path.join(buildWorkDir, panic.file); - } else { - panic.file = match[2]; // We failed to find a link to our source code, use Rust's - } - if (show) { - showPanic(panic); - } - } - return parsedQty; - } - function matchFunction(output) { const useJson = atom.config.get('build-cargo.jsonErrors'); const messages = []; // resulting collection of high-level messages @@ -287,12 +119,12 @@ export function provideBuilder() { line = line.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ''); } // Cargo final error messages start with 'error:', skip them - if (line === null || line === '' || line.substring(0, 6) === 'error:') { + if (line === null || line === '' || line.startsWith('error:')) { msg = null; sub = null; } else if (useJson && line[0] === '{') { // Parse a JSON block - parseJsonMessage(line, messages); + jsonParser.parseMessage(line, messages); } else { // Check for compilation messages const match = /^(.+):(\d+):(\d+):(?: (\d+):(\d+))? (error|warning|help|note): (.*)/g.exec(line); @@ -304,7 +136,7 @@ export function provideBuilder() { let endCol = match[5]; const level = match[6]; const message = match[7]; - if (level === 'error' || level === 'warning' || msg === null) { + if (level === 'error' || level === 'warning') { msg = { message: message, file: filePath, @@ -312,8 +144,8 @@ export function provideBuilder() { line_end: endLine, col: startCol, col_end: endCol, - type: level2type(level), - severity: level2severity(level), + type: err.level2type(level), + severity: err.level2severity(level), trace: [] }; messages.push(msg); @@ -326,7 +158,7 @@ export function provideBuilder() { startCol = undefined; endLine = undefined; endCol = undefined; - } else if (msg.file.startsWith('<')) { + } else if (msg && msg.file.startsWith('<')) { // The root message has incorrect file link, use the one from the extended messsage msg.file = filePath; msg.line = startLine; @@ -334,21 +166,23 @@ export function provideBuilder() { msg.col = startCol; msg.col_end = endCol; } - sub = { - message: message, - file: filePath, - line: startLine, - line_end: endLine, - col: startCol, - col_end: endCol, - type: level2type(level), - severity: level2severity(level) - }; - msg.trace.push(sub); + if (msg) { + sub = { + message: message, + file: filePath, + line: startLine, + line_end: endLine, + col: startCol, + col_end: endCol, + type: err.level2type(level), + severity: err.level2severity(level) + }; + msg.trace.push(sub); + } } } else { // Check for panic - const parsedQty = tryParsePanic(lines, i, panicsN < panicsLimit); + const parsedQty = panicParser.tryParsePanic(lines, i, panicsN < panicsLimit, buildWorkDir); if (parsedQty > 0) { msg = null; sub = null; @@ -368,7 +202,9 @@ export function provideBuilder() { } else if (hiddenPanicsN > 1) { atom.notifications.addError(hiddenPanicsN + ' more panics are hidden', { dismissable: true }); } - return messages; + return messages.filter(function(msg) { + return err.preprocessMessage(msg, buildWorkDir); + }); } // Checks if the given object represents the root of the project or file system diff --git a/lib/errors.js b/lib/errors.js new file mode 100644 index 0000000..27eba96 --- /dev/null +++ b/lib/errors.js @@ -0,0 +1,33 @@ +'use babel'; + +// +// Utility functions for parsing errors +// + +const level2severity = (level) => { + switch (level) { + case 'warning': return 'warning'; + case 'error': return 'error'; + case 'note': return 'info'; + case 'help': return 'info'; + default: return 'error'; + } +}; + +const level2type = (level) => { + return level.charAt(0).toUpperCase() + level.slice(1); +}; + +// Set location for special cases when the compiler doesn't provide it +function preprocessMessage(msg, buildWorkDir) { + if (msg.file) { + return true; + } + if (msg.message === 'main function not found') { + msg.file = buildWorkDir + '/src/main.rs'; // TODO: When running an example, set the path to the example file. + return true; + } + return false; +} + +export { level2severity, level2type, preprocessMessage }; diff --git a/lib/json-parser.js b/lib/json-parser.js new file mode 100644 index 0000000..941fb36 --- /dev/null +++ b/lib/json-parser.js @@ -0,0 +1,250 @@ +'use babel'; + +// +// JSON error format parser. +// + +const err = require('./errors'); + +// Collection of span labels that must be ignored (not added to the main message) +// because the main message already contains the same information +const redundantLabels = [{ + // E0001 + label: /this is an unreachable pattern/, + message: /unreachable pattern/ +}, { + // E0004 + label: /pattern `.+` not covered/, + message: /non-exhaustive patterns: `.+` not covered/ +}, { + // E00023 + label: /expected \d+ fields, found \d+/, + message: /this pattern has \d+ field, but the corresponding variant has \d+ fields/ +}, { + // E0026 + label: /struct `.+` does not have field `.+`/, + message: /struct `.+` does not have a field named `.+`/ +}, { + // E0027 + label: /missing field `.+`/, + message: /pattern does not mention field `.+`/ +}, { + // E0029 + label: /ranges require char or numeric types/, + message: /only char and numeric types are allowed in range patterns/ +}, { + // E0040 + label: /call to destructor method/, + message: /explicit use of destructor method/ +}, { + // E0046 + label: /missing `.+` in implementation/, + message: /not all trait items implemented, missing: `.+`/ +}, { + // E0057 + label: /expected \d+ parameter[s]?/, + message: /this function takes \d+ parameter[s]? but \d+ parameter[s]? (was|were) supplied/ +}, { + // E0062 + label: /used more than once/, + message: /field `.+` specified more than once/ +}, { + // E0067 + label: /invalid expression for left-hand side/, + message: /invalid left-hand side expression/ +}, { + // E0068 + label: /return type is not \(\)/, + message: /`return;` in a function whose return type is not `\(\)`/ +}, { + // E0071 + label: /not a struct/, + message: /`.+` does not name a struct or a struct variant/ +}, { + // E0072 + label: /recursive type has infinite size/, + message: /recursive type `.+` has infinite size/ +}, { + // E0087 + label: /expected \d+ parameter[s]?/, + message: /too many type parameters provided: expected at most \d+ parameter[s]?, found \d+ parameter[s]?/ +}, { + // E0091 + label: /unused type parameter/, + message: /type parameter `.+` is unused/ +}, { + // E0101 + label: /cannot resolve type of expression/, + message: /cannot determine a type for this expression: unconstrained type/ +}, { + // E0102 + label: /cannot resolve type of variable/, + message: /cannot determine a type for this local variable: unconstrained type/ +}, { + // E0106 + label: /expected lifetime parameter/, + message: /missing lifetime specifier/ +}, { + // E0107 + label: /(un)?expected (\d+ )?lifetime parameter[s]?/, + message: /wrong number of lifetime parameters: expected \d+, found \d+/ +}, { + // E0109 + label: /type parameter not allowed/, + message: /type parameters are not allowed on this type/ +}, { + // E0110 + label: /lifetime parameter not allowed/, + message: /lifetime parameters are not allowed on this type/ +}, { + // E0116 + label: /impl for type defined outside of crate/, + message: /cannot define inherent `.+` for a type outside of the crate where the type is defined/ +}, { + // E0117 + label: /impl doesn't use types inside crate/, + message: /only traits defined in the current crate can be implemented for arbitrary types/ +}, { + // E0119 + label: /conflicting implementation for `.+`/, + message: /conflicting implementations of trait `.+` for type `.+`/ +}, { + // E0120 + label: /implementing Drop requires a struct/, + message: /the Drop trait may only be implemented on structures/ +}, { + // E0121 + label: /not allowed in type signatures/, + message: /the type placeholder `_` is not allowed within types on item signatures/ +}, { + // E0124 + label: /field already declared/, + message: /field `.+` is already declared/ +}, { + // E0368 + label: /cannot use `[<>+&|^\-]?=` on type `.+`/, + message: /binary assignment operation `[<>+&|^\-]?=` cannot be applied to type `.+`/ +}, { + // E0387 + label: /cannot borrow mutably/, + message: /cannot borrow immutable local variable `.+` as mutable/ +}]; + +// Copies a location from the given span to a linter message +function copySpanLocation(span, msg) { + msg.file = span.file_name; + msg.line = span.line_start; + msg.line_end = span.line_end; + msg.col = span.column_start; + msg.col_end = span.column_end; +} + +// Checks if the location of the given span is the same as the location +// of the given message +function compareLocations(span, msg) { + return span.file_name === msg.file + && span.line_start === msg.line + && span.line_end === msg.line_end + && span.column_start === msg.col + && span.column_end === msg.col_end +} + +// Appends spans's lable to the main message. It only adds the lable if: +// - the main message doesn't contain exactly the same phrase +// - the main message doesn't contain the same information but uses different wording +function appendSpanLabel(span, msg) { + if (!span.label || msg.message.indexOf(span.label) >= 0) { + return; + } + for(let idx in redundantLabels) { + const l = redundantLabels[idx]; + if (l.label.test(span.label) && l.message.test(msg.message)) { + return; + } + } + msg.message += ' (' + span.label + ')' +} + +function parseSpan(span, msg, mainMsg) { + if (span.is_primary) { + appendSpanLabel(span, msg); + // If the error is within a macro, add the macro text to the message + if (span.file_name && span.file_name.startsWith('<') && span.text && span.text.length > 0) { + msg.trace.push({ + message: span.text[0].text, + type: 'Macro', + severity: 'info' + }); + } + } + if (span.file_name && !span.file_name.startsWith('<')) { + if (!span.is_primary && span.label) { + // A secondary span + const trace = { + message: span.label, + type: 'Note', + severity: 'info' + }; + // Add location only if it's not the same as in the primary span + // or if the primary span is unknown at this point + if (!compareLocations(span, mainMsg)) { + copySpanLocation(span, trace); + } + msg.trace.push(trace); + } + // Copy the main error location from the primary span or from any other + // span if it hasn't been defined yet + if (span.is_primary || !msg.file) { + if (!compareLocations(span, mainMsg)) { + copySpanLocation(span, msg); + } + } + return true; + } else if (span.expansion) { + return parseSpan(span.expansion.span, msg, mainMsg); + } + return false; +} + +// Parses spans of the given message +function parseSpans(jsonObj, msg, mainMsg) { + if (jsonObj.spans) { + jsonObj.spans.forEach(span => parseSpan(span, msg, mainMsg)); + } +} + +// Parses a compile message in the JSON format +const parseMessage = (line, messages) => { + const json = JSON.parse(line); + const msg = { + message: json.message, + type: err.level2type(json.level), + severity: err.level2severity(json.level), + trace: [] + }; + parseSpans(json, msg, msg); + json.children.forEach(child => { + const tr = { + message: child.message, + type: err.level2type(child.level), + severity: err.level2severity(child.level) + }; + parseSpans(child, tr, msg); + msg.trace.push(tr); + }); + if (json.code) { + if (json.code.code) { + msg.message += ' [' + json.code.code + ']' + } + if (json.code.explanation) { + msg.trace.push({ + message: json.code.explanation, + type: 'Explanation', + severity: 'info' + }); + } + } + messages.push(msg); +}; + +export { parseMessage }; diff --git a/lib/panic-parser.js b/lib/panic-parser.js new file mode 100644 index 0000000..0fe8374 --- /dev/null +++ b/lib/panic-parser.js @@ -0,0 +1,112 @@ +'use babel'; + +// +// Panics and stack backtrades parser. +// + +const path = require('path'); + +// Constants to detect links to Rust's source code and make them followable +const unixRustSrcPrefix = '../src/'; +const windowsRustSrcPrefix = '..\\src\\'; + +let panicsCounter = 0; // Counts all panics + +// Checks if the given file path returned by rustc or cargo points to the Rust source code +function isRustSourceLink(filePath) { + return filePath.startsWith(unixRustSrcPrefix) || filePath.startsWith(windowsRustSrcPrefix); +} + +// Shows panic info +function showPanic(panic) { + // Only add link if we have panic.filePath, otherwise it's an external link + atom.notifications.addError( + 'A thread panicked at ' + + (panic.filePath ? '' : '') + + 'line ' + panic.line + ' in ' + panic.file + + (panic.filePath ? '' : ''), { + detail: panic.message, + stack: panic.stack, + dismissable: true + }); + if (panic.filePath) { + const link = document.getElementById(panic.id); + if (link) { + link.panic = panic; + link.addEventListener('click', function (e) { + atom.workspace.open(e.target.panic.filePath, { + searchAllPanes: true, + initialLine: e.target.panic.line - 1 + }); + }); + } + } +} + +// Tries to parse a stack trace. Returns the quantity of actually parsed lines. +function tryParseStackTrace(lines, i, panic) { + let parsedQty = 0; + let line = lines[i]; + if (line.substring(0, 16) === 'stack backtrace:') { + parsedQty += 1; + const panicLines = []; + for (let j = i + 1; j < lines.length; j++) { + line = lines[j]; + const matchFunc = /^(\s+\d+):\s+0x[a-f0-9]+ - (?:(.+)::h[0-9a-f]+|(.+))$/g.exec(line); + if (matchFunc) { + // A line with a function call + if (atom.config.get('build-cargo.backtraceType') === 'Compact') { + line = matchFunc[1] + ': ' + (matchFunc[2] || matchFunc[3]); + } + panicLines.push(line); + } else { + const matchLink = /(at (.+):(\d+))$/g.exec(line); + if (matchLink) { + // A line with a file link + if (!panic.file && !isRustSourceLink(matchLink[2])) { + panic.file = matchLink[2]; // Found a link to our source code + panic.line = matchLink[3]; + } + panicLines.push(' ' + matchLink[1]); // less leading spaces + } else { + // Stack trace has ended + break; + } + } + parsedQty += 1; + } + panic.stack = panicLines.join('\n'); + } + return parsedQty; +} + +// Tries to parse a panic and its stack trace. Returns the quantity of actually +// parsed lines. +const tryParsePanic = (lines, i, show, buildWorkDir) => { + const line = lines[i]; + const match = /(thread '.+' panicked at '.+'), ([^\/][^\:]+):(\d+)/g.exec(line); + let parsedQty = 0; + if (match) { + parsedQty = 1; + const panic = { + id: 'build-cargo-panic-' + (++panicsCounter), // Unique panic ID + message: match[1], + file: isRustSourceLink(match[2]) ? undefined : match[2], + filePath: undefined, + line: parseInt(match[3], 10), + stack: undefined + }; + parsedQty = 1 + tryParseStackTrace(lines, i + 1, panic); + if (panic.file) { + panic.filePath = path.isAbsolute(panic.file) ? panic.file : path.join(buildWorkDir, panic.file); + } else { + panic.file = match[2]; // We failed to find a link to our source code, use Rust's + } + if (show) { + showPanic(panic); + } + } + return parsedQty; +}; + +export { tryParsePanic }; diff --git a/styles/linter.less b/styles/linter.less index 4b15f2b..a3711fc 100644 --- a/styles/linter.less +++ b/styles/linter.less @@ -26,6 +26,18 @@ atom-text-editor::shadow .linter-highlight, .linter-highlight{ border-bottom: 1px dashed @background-color-info; } } + &.macro { + &:not(.line-number){ + background-color: @background-color-info; + color: white; + } + .linter-gutter{ + color: @background-color-info; + } + .region { + border-bottom: 1px dashed @background-color-info; + } + } &.panic { &:not(.line-number){ background-color: @background-color-error; From 785079f1d2d7165f493d7873139c1cdbae7c6de5 Mon Sep 17 00:00:00 2001 From: Andrew Lygin Date: Sat, 17 Sep 2016 23:18:48 +0300 Subject: [PATCH 2/8] Fixed formatting --- lib/cargo.js | 6 +++--- lib/errors.js | 2 +- lib/json-parser.js | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/cargo.js b/lib/cargo.js index 91edee2..1f5bf6f 100644 --- a/lib/cargo.js +++ b/lib/cargo.js @@ -100,7 +100,7 @@ export function provideBuilder() { const path = require('path'); const err = require('./errors'); const jsonParser = require('./json-parser'); - const panicParser = require('./panic-parser') + const panicParser = require('./panic-parser'); let buildWorkDir; // The last build workding directory (might differ from the project root for multi-crate projects) const panicsLimit = 10; // Max number of panics to show at once @@ -202,8 +202,8 @@ export function provideBuilder() { } else if (hiddenPanicsN > 1) { atom.notifications.addError(hiddenPanicsN + ' more panics are hidden', { dismissable: true }); } - return messages.filter(function(msg) { - return err.preprocessMessage(msg, buildWorkDir); + return messages.filter(function (m) { + return err.preprocessMessage(m, buildWorkDir); }); } diff --git a/lib/errors.js b/lib/errors.js index 27eba96..d227dca 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -5,7 +5,7 @@ // const level2severity = (level) => { - switch (level) { + switch (level) { case 'warning': return 'warning'; case 'error': return 'error'; case 'note': return 'info'; diff --git a/lib/json-parser.js b/lib/json-parser.js index 941fb36..5bc0d65 100644 --- a/lib/json-parser.js +++ b/lib/json-parser.js @@ -146,7 +146,7 @@ function compareLocations(span, msg) { && span.line_start === msg.line && span.line_end === msg.line_end && span.column_start === msg.col - && span.column_end === msg.col_end + && span.column_end === msg.col_end; } // Appends spans's lable to the main message. It only adds the lable if: @@ -156,13 +156,13 @@ function appendSpanLabel(span, msg) { if (!span.label || msg.message.indexOf(span.label) >= 0) { return; } - for(let idx in redundantLabels) { + for (let idx = 0; idx < redundantLabels.length; idx++) { const l = redundantLabels[idx]; if (l.label.test(span.label) && l.message.test(msg.message)) { return; } } - msg.message += ' (' + span.label + ')' + msg.message += ' (' + span.label + ')'; } function parseSpan(span, msg, mainMsg) { @@ -188,7 +188,7 @@ function parseSpan(span, msg, mainMsg) { // Add location only if it's not the same as in the primary span // or if the primary span is unknown at this point if (!compareLocations(span, mainMsg)) { - copySpanLocation(span, trace); + copySpanLocation(span, trace); } msg.trace.push(trace); } @@ -234,7 +234,7 @@ const parseMessage = (line, messages) => { }); if (json.code) { if (json.code.code) { - msg.message += ' [' + json.code.code + ']' + msg.message += ' [' + json.code.code + ']'; } if (json.code.explanation) { msg.trace.push({ From 4276bdedd262360a6657ce1c834137fd8e77d2f0 Mon Sep 17 00:00:00 2001 From: Andrew Lygin Date: Sat, 17 Sep 2016 23:25:46 +0300 Subject: [PATCH 3/8] Fixed formatting --- lib/errors.js | 30 ++++----- lib/panic-parser.js | 158 ++++++++++++++++++++++---------------------- 2 files changed, 94 insertions(+), 94 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index d227dca..ab2a7e4 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -6,28 +6,28 @@ const level2severity = (level) => { switch (level) { - case 'warning': return 'warning'; - case 'error': return 'error'; - case 'note': return 'info'; - case 'help': return 'info'; - default: return 'error'; - } + case 'warning': return 'warning'; + case 'error': return 'error'; + case 'note': return 'info'; + case 'help': return 'info'; + default: return 'error'; + } }; const level2type = (level) => { - return level.charAt(0).toUpperCase() + level.slice(1); + return level.charAt(0).toUpperCase() + level.slice(1); }; // Set location for special cases when the compiler doesn't provide it function preprocessMessage(msg, buildWorkDir) { - if (msg.file) { - return true; - } - if (msg.message === 'main function not found') { - msg.file = buildWorkDir + '/src/main.rs'; // TODO: When running an example, set the path to the example file. - return true; - } - return false; + if (msg.file) { + return true; + } + if (msg.message === 'main function not found') { + msg.file = buildWorkDir + '/src/main.rs'; // TODO: When running an example, set the path to the example file. + return true; + } + return false; } export { level2severity, level2type, preprocessMessage }; diff --git a/lib/panic-parser.js b/lib/panic-parser.js index 0fe8374..51a2f36 100644 --- a/lib/panic-parser.js +++ b/lib/panic-parser.js @@ -19,94 +19,94 @@ function isRustSourceLink(filePath) { // Shows panic info function showPanic(panic) { - // Only add link if we have panic.filePath, otherwise it's an external link - atom.notifications.addError( - 'A thread panicked at ' - + (panic.filePath ? '' : '') - + 'line ' + panic.line + ' in ' + panic.file - + (panic.filePath ? '' : ''), { - detail: panic.message, - stack: panic.stack, - dismissable: true - }); - if (panic.filePath) { - const link = document.getElementById(panic.id); - if (link) { - link.panic = panic; - link.addEventListener('click', function (e) { - atom.workspace.open(e.target.panic.filePath, { - searchAllPanes: true, - initialLine: e.target.panic.line - 1 - }); - }); - } - } + // Only add link if we have panic.filePath, otherwise it's an external link + atom.notifications.addError( + 'A thread panicked at ' + + (panic.filePath ? '' : '') + + 'line ' + panic.line + ' in ' + panic.file + + (panic.filePath ? '' : ''), { + detail: panic.message, + stack: panic.stack, + dismissable: true + }); + if (panic.filePath) { + const link = document.getElementById(panic.id); + if (link) { + link.panic = panic; + link.addEventListener('click', function (e) { + atom.workspace.open(e.target.panic.filePath, { + searchAllPanes: true, + initialLine: e.target.panic.line - 1 + }); + }); + } + } } // Tries to parse a stack trace. Returns the quantity of actually parsed lines. function tryParseStackTrace(lines, i, panic) { - let parsedQty = 0; - let line = lines[i]; - if (line.substring(0, 16) === 'stack backtrace:') { - parsedQty += 1; - const panicLines = []; - for (let j = i + 1; j < lines.length; j++) { - line = lines[j]; - const matchFunc = /^(\s+\d+):\s+0x[a-f0-9]+ - (?:(.+)::h[0-9a-f]+|(.+))$/g.exec(line); - if (matchFunc) { - // A line with a function call - if (atom.config.get('build-cargo.backtraceType') === 'Compact') { - line = matchFunc[1] + ': ' + (matchFunc[2] || matchFunc[3]); - } - panicLines.push(line); - } else { - const matchLink = /(at (.+):(\d+))$/g.exec(line); - if (matchLink) { - // A line with a file link - if (!panic.file && !isRustSourceLink(matchLink[2])) { - panic.file = matchLink[2]; // Found a link to our source code - panic.line = matchLink[3]; - } - panicLines.push(' ' + matchLink[1]); // less leading spaces - } else { - // Stack trace has ended - break; - } - } - parsedQty += 1; - } - panic.stack = panicLines.join('\n'); - } - return parsedQty; + let parsedQty = 0; + let line = lines[i]; + if (line.substring(0, 16) === 'stack backtrace:') { + parsedQty += 1; + const panicLines = []; + for (let j = i + 1; j < lines.length; j++) { + line = lines[j]; + const matchFunc = /^(\s+\d+):\s+0x[a-f0-9]+ - (?:(.+)::h[0-9a-f]+|(.+))$/g.exec(line); + if (matchFunc) { + // A line with a function call + if (atom.config.get('build-cargo.backtraceType') === 'Compact') { + line = matchFunc[1] + ': ' + (matchFunc[2] || matchFunc[3]); + } + panicLines.push(line); + } else { + const matchLink = /(at (.+):(\d+))$/g.exec(line); + if (matchLink) { + // A line with a file link + if (!panic.file && !isRustSourceLink(matchLink[2])) { + panic.file = matchLink[2]; // Found a link to our source code + panic.line = matchLink[3]; + } + panicLines.push(' ' + matchLink[1]); // less leading spaces + } else { + // Stack trace has ended + break; + } + } + parsedQty += 1; + } + panic.stack = panicLines.join('\n'); + } + return parsedQty; } // Tries to parse a panic and its stack trace. Returns the quantity of actually // parsed lines. const tryParsePanic = (lines, i, show, buildWorkDir) => { - const line = lines[i]; - const match = /(thread '.+' panicked at '.+'), ([^\/][^\:]+):(\d+)/g.exec(line); - let parsedQty = 0; - if (match) { - parsedQty = 1; - const panic = { - id: 'build-cargo-panic-' + (++panicsCounter), // Unique panic ID - message: match[1], - file: isRustSourceLink(match[2]) ? undefined : match[2], - filePath: undefined, - line: parseInt(match[3], 10), - stack: undefined - }; - parsedQty = 1 + tryParseStackTrace(lines, i + 1, panic); - if (panic.file) { - panic.filePath = path.isAbsolute(panic.file) ? panic.file : path.join(buildWorkDir, panic.file); - } else { - panic.file = match[2]; // We failed to find a link to our source code, use Rust's - } - if (show) { - showPanic(panic); - } - } - return parsedQty; + const line = lines[i]; + const match = /(thread '.+' panicked at '.+'), ([^\/][^\:]+):(\d+)/g.exec(line); + let parsedQty = 0; + if (match) { + parsedQty = 1; + const panic = { + id: 'build-cargo-panic-' + (++panicsCounter), // Unique panic ID + message: match[1], + file: isRustSourceLink(match[2]) ? undefined : match[2], + filePath: undefined, + line: parseInt(match[3], 10), + stack: undefined + }; + parsedQty = 1 + tryParseStackTrace(lines, i + 1, panic); + if (panic.file) { + panic.filePath = path.isAbsolute(panic.file) ? panic.file : path.join(buildWorkDir, panic.file); + } else { + panic.file = match[2]; // We failed to find a link to our source code, use Rust's + } + if (show) { + showPanic(panic); + } + } + return parsedQty; }; export { tryParsePanic }; From efd03e7d4c418d2b09b3aeabb10dd6c0aa873b2d Mon Sep 17 00:00:00 2001 From: Andrew Lygin Date: Sun, 18 Sep 2016 15:38:59 +0300 Subject: [PATCH 4/8] Display errors without locations as notifications --- lib/errors.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index ab2a7e4..e62fc7e 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -4,6 +4,8 @@ // Utility functions for parsing errors // +const notificationCfg = { dismissable: true }; + const level2severity = (level) => { switch (level) { case 'warning': return 'warning'; @@ -23,9 +25,20 @@ function preprocessMessage(msg, buildWorkDir) { if (msg.file) { return true; } - if (msg.message === 'main function not found') { - msg.file = buildWorkDir + '/src/main.rs'; // TODO: When running an example, set the path to the example file. - return true; + if (msg.message !== 'aborting due to previous error') { // This meta error is ignored + // Location is not provided for the message, so it cannot be added to Linter. + // Display it as a notification. + switch (msg.level) { + case 'info': + case 'note': + atom.notifications.addInfo(msg.message, notificationCfg); + break; + case 'warning': + atom.notifications.addWarning(msg.message, notificationCfg); + break; + default: + atom.notifications.addError(msg.message, notificationCfg); + } } return false; } From 6d55f2adb0189895bc6829cd296dd0938916d177 Mon Sep 17 00:00:00 2001 From: Andrew Lygin Date: Sun, 18 Sep 2016 15:46:26 +0300 Subject: [PATCH 5/8] Reset "JSON error format" setting --- lib/cargo.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/cargo.js b/lib/cargo.js index 1f5bf6f..8927e1f 100644 --- a/lib/cargo.js +++ b/lib/cargo.js @@ -16,6 +16,7 @@ if (atom.config.get('build-cargo.cargoClippy')) { atom.config.unset('build-cargo.showBacktrace'); atom.config.unset('build-cargo.cargoCheck'); atom.config.unset('build-cargo.cargoClippy'); +atom.config.unset('build-cargo.jsonErrors'); export const config = { cargoPath: { @@ -46,7 +47,7 @@ export const config = { enum: [ 'Off', 'Compact', 'Full' ], order: 4 }, - jsonErrors: { + jsonErrorFormat: { title: 'Use JSON error format', description: 'Use JSON error format instead of human readable output. When switched off, Linter is not used to display compiler messages.', type: 'boolean', @@ -106,7 +107,7 @@ export function provideBuilder() { const panicsLimit = 10; // Max number of panics to show at once function matchFunction(output) { - const useJson = atom.config.get('build-cargo.jsonErrors'); + const useJson = atom.config.get('build-cargo.jsonErrorFormat'); const messages = []; // resulting collection of high-level messages let msg = null; // current high-level message (error, warning or panic) let sub = null; // current submessage (note or help) @@ -250,7 +251,7 @@ export function provideBuilder() { // Common build command parameters buildCfg.exec = atom.config.get('build-cargo.cargoPath'); buildCfg.env = {}; - if (atom.config.get('build-cargo.jsonErrors')) { + if (atom.config.get('build-cargo.jsonErrorFormat')) { buildCfg.env.RUSTFLAGS = '-Z unstable-options --error-format=json'; } else if (process.platform !== 'win32') { buildCfg.env.TERM = 'xterm'; From 0116901ce4bcd210cead523b25800b86a510d896 Mon Sep 17 00:00:00 2001 From: Andrew Lygin Date: Sun, 18 Sep 2016 19:37:06 +0300 Subject: [PATCH 6/8] Fixed suppressing of the compilation abortion message for multiple errors --- lib/errors.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/errors.js b/lib/errors.js index e62fc7e..7aa119b 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -5,6 +5,7 @@ // const notificationCfg = { dismissable: true }; +const abortRegex = /aborting due to (\d+ )?previous error[s]?/; const level2severity = (level) => { switch (level) { @@ -25,7 +26,7 @@ function preprocessMessage(msg, buildWorkDir) { if (msg.file) { return true; } - if (msg.message !== 'aborting due to previous error') { // This meta error is ignored + if (!abortRegex.test(msg.message)) { // This meta error is ignored // Location is not provided for the message, so it cannot be added to Linter. // Display it as a notification. switch (msg.level) { From af1fcee7b6026ed4eb07f657b92ba328c1a90670 Mon Sep 17 00:00:00 2001 From: Andrew Lygin Date: Mon, 19 Sep 2016 13:21:38 +0300 Subject: [PATCH 7/8] Minor refactoring and typo fixes --- lib/json-parser.js | 4 ++-- lib/panic-parser.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/json-parser.js b/lib/json-parser.js index 5bc0d65..866507d 100644 --- a/lib/json-parser.js +++ b/lib/json-parser.js @@ -149,14 +149,14 @@ function compareLocations(span, msg) { && span.column_end === msg.col_end; } -// Appends spans's lable to the main message. It only adds the lable if: +// Appends spans's label to the main message. It only adds the label if: // - the main message doesn't contain exactly the same phrase // - the main message doesn't contain the same information but uses different wording function appendSpanLabel(span, msg) { if (!span.label || msg.message.indexOf(span.label) >= 0) { return; } - for (let idx = 0; idx < redundantLabels.length; idx++) { + for (idx in redundantLabels) { const l = redundantLabels[idx]; if (l.label.test(span.label) && l.message.test(msg.message)) { return; diff --git a/lib/panic-parser.js b/lib/panic-parser.js index 51a2f36..2456f57 100644 --- a/lib/panic-parser.js +++ b/lib/panic-parser.js @@ -1,7 +1,7 @@ 'use babel'; // -// Panics and stack backtrades parser. +// Panics and stack backtraces parser. // const path = require('path'); From 9b241fddc6401d8ac57e92f19220aeb9286506a4 Mon Sep 17 00:00:00 2001 From: Andrew Lygin Date: Mon, 19 Sep 2016 13:45:49 +0300 Subject: [PATCH 8/8] Fixed failed Travis check --- lib/json-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/json-parser.js b/lib/json-parser.js index 866507d..aafa8a9 100644 --- a/lib/json-parser.js +++ b/lib/json-parser.js @@ -156,7 +156,7 @@ function appendSpanLabel(span, msg) { if (!span.label || msg.message.indexOf(span.label) >= 0) { return; } - for (idx in redundantLabels) { + for (let idx = 0; idx < redundantLabels.length; idx++) { const l = redundantLabels[idx]; if (l.label.test(span.label) && l.message.test(msg.message)) { return;