From b9393a948b14989d254f08872e1419b754878bfc Mon Sep 17 00:00:00 2001 From: Wowbagger's Liquid Lunch <55120045+WowbaggersLiquidLunch@users.noreply.github.com> Date: Mon, 9 Nov 2020 15:58:43 -0500 Subject: [PATCH 1/7] =?UTF-8?q?fix=20typo:=20"provie"=20=E2=86=92=20"provi?= =?UTF-8?q?de"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/CartonHelpers/Parsers/DiagnosticsParser.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift index 823aebb9..13d88614 100644 --- a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift +++ b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift @@ -158,7 +158,7 @@ public struct DiagnosticsParser: ProcessOutputParser { guard messages.count > 0 else { continue } terminal.write("\(" \(file) ", color: "[1m", "[7m")") // bold, reversed terminal.write(" \(messages.first!.file)\(messages.first!.line)\n\n", inColor: .grey) - // Group messages that occur on sequential lines to provie a more readable output + // Group messages that occur on sequential lines to provide a more readable output var groupedMessages = [[CustomDiagnostic]]() for message in messages { if let lastLineStr = groupedMessages.last?.last?.line, From cccb09335ba63b50ff3b9ebd3eba5a42c154bec1 Mon Sep 17 00:00:00 2001 From: Wowbagger's Liquid Lunch <55120045+WowbaggersLiquidLunch@users.noreply.github.com> Date: Mon, 9 Nov 2020 16:38:47 -0500 Subject: [PATCH 2/7] improve code formatting --- Sources/CartonHelpers/Parsers/DiagnosticsParser.swift | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift index 13d88614..e064502a 100644 --- a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift +++ b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift @@ -208,14 +208,8 @@ public struct DiagnosticsParser: ProcessOutputParser { // Output the code for this line, syntax highlighted let paddedLine = message.line.padding(toLength: maxLine, withPad: " ", startingAt: 0) let highlightedCode = Self.highlighter.highlight(message.code) - terminal - .write( - " \("\(paddedLine) | ", color: "[36m")\(highlightedCode)\n" - ) // 36: cyan - terminal.write( - " " + "".padding(toLength: maxLine, withPad: " ", startingAt: 0) + " | ", - inColor: .cyan - ) + terminal.write(" \("\(paddedLine) | ", color: "[36m")\(highlightedCode)\n") // 36: cyan + terminal.write(" " + "".padding(toLength: maxLine, withPad: " ", startingAt: 0) + " | ", inColor: .cyan) // Aggregate the indicators (^ point to the error) onto a single line var charIndicators = String(repeating: " ", count: Int(message.char)!) + "^" From aeaba85458372916bbd712cb0b166a8ab6824992 Mon Sep 17 00:00:00 2001 From: Wowbagger's Liquid Lunch <55120045+WowbaggersLiquidLunch@users.noreply.github.com> Date: Mon, 9 Nov 2020 17:21:42 -0500 Subject: [PATCH 3/7] refactor line number-related things --- .../Parsers/DiagnosticsParser.swift | 54 +++++++++++++------ .../CartonHelpers/Parsers/TestsParser.swift | 8 +-- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift index e064502a..2b6cf611 100644 --- a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift +++ b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift @@ -76,7 +76,8 @@ public struct DiagnosticsParser: ProcessOutputParser { struct CustomDiagnostic { let kind: Kind let file: String - let line: String.SubSequence + /// The number of the row in the source file that the diagnosis is for. + let lineNumber: Int let char: String.SubSequence let code: String let message: String @@ -130,7 +131,7 @@ public struct DiagnosticsParser: ProcessOutputParser { .trimmingCharacters(in: .whitespaces))) ?? .note, file: file, - line: components[0], + lineNumber: Int(components[0]), char: components[1], code: String(lines[lineIdx]), message: components.dropFirst(3).joined(separator: ":") @@ -157,14 +158,13 @@ public struct DiagnosticsParser: ProcessOutputParser { for (file, messages) in diagnostics.sorted(by: { $0.key < $1.key }) { guard messages.count > 0 else { continue } terminal.write("\(" \(file) ", color: "[1m", "[7m")") // bold, reversed - terminal.write(" \(messages.first!.file)\(messages.first!.line)\n\n", inColor: .grey) + terminal.write(" \(messages.first!.file)\(messages.first!.lineNumber)\n\n", inColor: .grey) // Group messages that occur on sequential lines to provide a more readable output var groupedMessages = [[CustomDiagnostic]]() for message in messages { - if let lastLineStr = groupedMessages.last?.last?.line, - let lastLine = Int(lastLineStr), - let line = Int(message.line), - lastLine == line - 1 || lastLine == line + if let finalLineNumber = groupedMessages.last?.last?.lineNumber, + let currentLineNumber = message.lineNumber, + finalLineNumber == currentLineNumber - 1 || finalLineNumber == currentLineNumber { groupedMessages[groupedMessages.count - 1].append(message) } else { @@ -180,15 +180,29 @@ public struct DiagnosticsParser: ProcessOutputParser { " \(" \(kind) ", color: message.kind.color, "[37;1m") \(message.message)\n" ) // 37;1: bright white } - let maxLine = messages.map(\.line.count).max() ?? 0 + let greatestLineNumber = messages.map(\.lineNumber).max() ?? 0 + let numberOfDigitsInGreatestLineNumber = { + let (quotient, remainder) = greatestLineNumber.quotientAndRemainder(dividingBy: 10) + return quotient + (remainder == 0 ? 0 : 1) + } for (offset, message) in messages.enumerated() { if offset > 0 { // Make sure we don't log the same line twice - if messages[offset - 1].line != message.line { - flush(messages: messages, message: message, maxLine: maxLine, terminal) + if messages[offset - 1].lineNumber != message.lineNumber { + flush( + messages: messages, + message: message, + minimumSizeForLineNumbering: numberOfDigitsInGreatestLineNumber, + terminal: terminal + ) } } else { - flush(messages: messages, message: message, maxLine: maxLine, terminal) + flush( + messages: messages, + message: message, + minimumSizeForLineNumbering: numberOfDigitsInGreatestLineNumber, + terminal: terminal + ) } } terminal.write("\n") @@ -196,20 +210,28 @@ public struct DiagnosticsParser: ProcessOutputParser { terminal.write("\n") } } - + + /// <#Description#> + /// - Parameters: + /// - messages: <#messages description#> + /// - message: <#message description#> + /// - minimumSizeForLineNumbering: The minimum space that must be reserved for line numbers, so that they are well-aligned in the output. + /// - terminal: <#terminal description#> func flush( messages: [CustomDiagnostic], message: CustomDiagnostic, - maxLine: Int, - _ terminal: InteractiveWriter + minimumSizeForLineNumbering: Int, + terminal: InteractiveWriter ) { // Get all diagnostics for a particular line. - let allChars = messages.filter { $0.line == message.line }.map(\.char) + let allChars = messages.filter { $0.lineNumber == message.lineNumber }.map(\.char) // Output the code for this line, syntax highlighted - let paddedLine = message.line.padding(toLength: maxLine, withPad: " ", startingAt: 0) let highlightedCode = Self.highlighter.highlight(message.code) terminal.write(" \("\(paddedLine) | ", color: "[36m")\(highlightedCode)\n") // 36: cyan terminal.write(" " + "".padding(toLength: maxLine, withPad: " ", startingAt: 0) + " | ", inColor: .cyan) + /// A base-10 representation of the number of the row that the diagnosis is for, aligned vertically with all other rows. + let verticallyAlignedLineNumber = String(message.lineNumber, radix: 10).padding(toLength: minimumSizeForLineNumbering, withPad: " ", startingAt: 0) + terminal.write(" \("\(verticallyAlignedLineNumber) | ", color: "[36m")\(highlightedCode)\n") // 36: cyan // Aggregate the indicators (^ point to the error) onto a single line var charIndicators = String(repeating: " ", count: Int(message.char)!) + "^" diff --git a/Sources/CartonHelpers/Parsers/TestsParser.swift b/Sources/CartonHelpers/Parsers/TestsParser.swift index 6a361c66..ac17c062 100644 --- a/Sources/CartonHelpers/Parsers/TestsParser.swift +++ b/Sources/CartonHelpers/Parsers/TestsParser.swift @@ -196,7 +196,7 @@ public struct TestsParser: ProcessOutputParser { let diag = DiagnosticsParser.CustomDiagnostic( kind: DiagnosticsParser.CustomDiagnostic.Kind(rawValue: String(status)) ?? .note, file: String(path), - line: lineNum, + lineNumber: lineNum, char: "0", code: "", message: String(problem) @@ -255,7 +255,7 @@ public struct TestsParser: ProcessOutputParser { "\(testCase.name) \("(\(Int(Double(testCase.duration)! * 1000))ms)", color: "[90m")\n" ) // gray for problem in testCase.problems { - terminal.write("\n \(problem.file, color: "[90m"):\(problem.line)\n") + terminal.write("\n \(problem.file, color: "[90m"):\(problem.lineNumber)\n") terminal.write(" \(problem.message)\n\n") // Format XCTAssert functions for assertion in Regex.Assertion.allCases { @@ -268,7 +268,7 @@ public struct TestsParser: ProcessOutputParser { } } // Get the line of code from the file and output it for context. - if let lineNum = Int(problem.line), + if let lineNum = Int(problem.lineNumber), lineNum > 0 { var fileContents: String? @@ -285,7 +285,7 @@ public struct TestsParser: ProcessOutputParser { let fileLines = fileContents.components(separatedBy: .newlines) guard fileLines.count >= lineNum else { break } let highlightedCode = Self.highlighter.highlight(String(fileLines[lineNum - 1])) - terminal.write(" \("\(problem.line) | ", color: "[36m")\(highlightedCode)\n") + terminal.write(" \("\(problem.lineNumber) | ", color: "[36m")\(highlightedCode)\n") } } } From 724c30efe7c5428c5f0a22854014b6ca3e62c31d Mon Sep 17 00:00:00 2001 From: Wowbagger's Liquid Lunch <55120045+WowbaggersLiquidLunch@users.noreply.github.com> Date: Mon, 9 Nov 2020 17:29:36 -0500 Subject: [PATCH 4/7] replace `.padding()` with a String initialiser Avoidi Foundation for performance. --- Sources/CartonHelpers/Parsers/DiagnosticsParser.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift index 2b6cf611..d584433d 100644 --- a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift +++ b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift @@ -228,10 +228,11 @@ public struct DiagnosticsParser: ProcessOutputParser { // Output the code for this line, syntax highlighted let highlightedCode = Self.highlighter.highlight(message.code) terminal.write(" \("\(paddedLine) | ", color: "[36m")\(highlightedCode)\n") // 36: cyan - terminal.write(" " + "".padding(toLength: maxLine, withPad: " ", startingAt: 0) + " | ", inColor: .cyan) /// A base-10 representation of the number of the row that the diagnosis is for, aligned vertically with all other rows. let verticallyAlignedLineNumber = String(message.lineNumber, radix: 10).padding(toLength: minimumSizeForLineNumbering, withPad: " ", startingAt: 0) + // Each line of diagnostics output is indented with 2 spaces. terminal.write(" \("\(verticallyAlignedLineNumber) | ", color: "[36m")\(highlightedCode)\n") // 36: cyan + terminal.write(" \(String(repeating: " ", count: minimumSizeForLineNumbering)) | ", inColor: .cyan) // Aggregate the indicators (^ point to the error) onto a single line var charIndicators = String(repeating: " ", count: Int(message.char)!) + "^" From 0a790d075ee3188b586087cdd449b198522ea7a0 Mon Sep 17 00:00:00 2001 From: Wowbagger's Liquid Lunch <55120045+WowbaggersLiquidLunch@users.noreply.github.com> Date: Tue, 10 Nov 2020 12:06:13 -0500 Subject: [PATCH 5/7] refactor more line number-related things --- .../CartonHelpers/Parsers/DiagnosticsParser.swift | 11 ++++++----- Sources/CartonHelpers/Parsers/TestsParser.swift | 13 ++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift index d584433d..09e04e89 100644 --- a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift +++ b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift @@ -131,7 +131,8 @@ public struct DiagnosticsParser: ProcessOutputParser { .trimmingCharacters(in: .whitespaces))) ?? .note, file: file, - lineNumber: Int(components[0]), + // FIXME: We should handle this more gracefully than force-unwrapping it. + lineNumber: Int(components[0])!, char: components[1], code: String(lines[lineIdx]), message: components.dropFirst(3).joined(separator: ":") @@ -163,8 +164,8 @@ public struct DiagnosticsParser: ProcessOutputParser { var groupedMessages = [[CustomDiagnostic]]() for message in messages { if let finalLineNumber = groupedMessages.last?.last?.lineNumber, - let currentLineNumber = message.lineNumber, - finalLineNumber == currentLineNumber - 1 || finalLineNumber == currentLineNumber + // `message.lineNumber` is the current line number. + finalLineNumber == message.lineNumber - 1 || finalLineNumber == message.lineNumber { groupedMessages[groupedMessages.count - 1].append(message) } else { @@ -181,10 +182,10 @@ public struct DiagnosticsParser: ProcessOutputParser { ) // 37;1: bright white } let greatestLineNumber = messages.map(\.lineNumber).max() ?? 0 - let numberOfDigitsInGreatestLineNumber = { + let numberOfDigitsInGreatestLineNumber: Int = { let (quotient, remainder) = greatestLineNumber.quotientAndRemainder(dividingBy: 10) return quotient + (remainder == 0 ? 0 : 1) - } + }() for (offset, message) in messages.enumerated() { if offset > 0 { // Make sure we don't log the same line twice diff --git a/Sources/CartonHelpers/Parsers/TestsParser.swift b/Sources/CartonHelpers/Parsers/TestsParser.swift index ac17c062..3a353a5e 100644 --- a/Sources/CartonHelpers/Parsers/TestsParser.swift +++ b/Sources/CartonHelpers/Parsers/TestsParser.swift @@ -188,7 +188,8 @@ public struct TestsParser: ProcessOutputParser { ) } else if let problem = line.matches(regex: Regex.problem), let path = line.match(of: Regex.problem, labelled: .path), - let lineNum = line.match(of: Regex.problem, labelled: .line), + let lineNumberInBase10 = line.match(of: Regex.problem, labelled: .line), + let lineNumber = Int(lineNumberInBase10), let status = line.match(of: Regex.problem, labelled: .status), let suite = line.match(of: Regex.problem, labelled: .suite), let testCase = line.match(of: Regex.problem, labelled: .testCase) @@ -196,7 +197,7 @@ public struct TestsParser: ProcessOutputParser { let diag = DiagnosticsParser.CustomDiagnostic( kind: DiagnosticsParser.CustomDiagnostic.Kind(rawValue: String(status)) ?? .note, file: String(path), - lineNumber: lineNum, + lineNumber: lineNumber, char: "0", code: "", message: String(problem) @@ -268,9 +269,7 @@ public struct TestsParser: ProcessOutputParser { } } // Get the line of code from the file and output it for context. - if let lineNum = Int(problem.lineNumber), - lineNum > 0 - { + if problem.lineNumber > 0 { var fileContents: String? if let fileBuf = fileBufs.first(where: { $0.path == problem.file })?.contents { fileContents = fileBuf @@ -283,8 +282,8 @@ public struct TestsParser: ProcessOutputParser { } if let fileContents = fileContents { let fileLines = fileContents.components(separatedBy: .newlines) - guard fileLines.count >= lineNum else { break } - let highlightedCode = Self.highlighter.highlight(String(fileLines[lineNum - 1])) + guard fileLines.count >= problem.lineNumber else { break } + let highlightedCode = Self.highlighter.highlight(String(fileLines[problem.lineNumber - 1])) terminal.write(" \("\(problem.lineNumber) | ", color: "[36m")\(highlightedCode)\n") } } From ebde0906c6ae5385b7262b955a59655bdd63bf9f Mon Sep 17 00:00:00 2001 From: Wowbagger's Liquid Lunch <55120045+WowbaggersLiquidLunch@users.noreply.github.com> Date: Wed, 11 Nov 2020 13:39:19 -0500 Subject: [PATCH 6/7] [NFC] reformat CustomDiagnostic initialisation, for easier reading --- Sources/CartonHelpers/Parsers/DiagnosticsParser.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift index 09e04e89..a0e439c8 100644 --- a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift +++ b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift @@ -125,11 +125,8 @@ public struct DiagnosticsParser: ProcessOutputParser { .replacingOccurrences(of: ":", with: "") == String(currFile) else { continue } fileMessages.append( - .init( - kind: CustomDiagnostic - .Kind(rawValue: String(components[2] - .trimmingCharacters(in: .whitespaces))) ?? - .note, + CustomDiagnostic( + kind: CustomDiagnostic.Kind(rawValue: String(components[2].trimmingCharacters(in: .whitespaces))) ?? .note, file: file, // FIXME: We should handle this more gracefully than force-unwrapping it. lineNumber: Int(components[0])!, From 348cc94dccbafaa4e7b1312c5dbdf313271ce037 Mon Sep 17 00:00:00 2001 From: Wowbagger's Liquid Lunch <55120045+WowbaggersLiquidLunch@users.noreply.github.com> Date: Wed, 11 Nov 2020 13:49:58 -0500 Subject: [PATCH 7/7] strip leading whitespace from source code in diagnosis, and copy remaining whitespace verbatim to error location indicators' indentation --- .../Parsers/DiagnosticsParser.swift | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift index a0e439c8..76a6cb06 100644 --- a/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift +++ b/Sources/CartonHelpers/Parsers/DiagnosticsParser.swift @@ -224,22 +224,44 @@ public struct DiagnosticsParser: ProcessOutputParser { // Get all diagnostics for a particular line. let allChars = messages.filter { $0.lineNumber == message.lineNumber }.map(\.char) // Output the code for this line, syntax highlighted - let highlightedCode = Self.highlighter.highlight(message.code) - terminal.write(" \("\(paddedLine) | ", color: "[36m")\(highlightedCode)\n") // 36: cyan /// A base-10 representation of the number of the row that the diagnosis is for, aligned vertically with all other rows. let verticallyAlignedLineNumber = String(message.lineNumber, radix: 10).padding(toLength: minimumSizeForLineNumbering, withPad: " ", startingAt: 0) + /// The line of code that the diagnostics message is for. + let sourceLine = message.code + // The following 2 assignments remove the leading whitespace from each line of code in the diagnostics. + // Although technically, we should remove only horizontal whitespace characters, but since there is no vertical whitespace in a continuous line, we can safely remove all whitespace characters. + /// The position of the first non-whitespace character in the line of code that the diagnostics message is for. + /// + /// If no such character exists, then the position is the same as the line's `endIndex`. + let firstIndexOfNonWhitespaceCharacterInSourceLine = sourceLine.firstIndex(where: { !$0.isWhitespace } ) ?? sourceLine.endIndex + /// The line of code that the diagnostics message is for, with leading whitespace removed. + let sourceLineSansWhitespace = sourceLine[firstIndexOfNonWhitespaceCharacterInSourceLine...] + let highlightedCode = Self.highlighter.highlight(String(sourceLineSansWhitespace)) // Each line of diagnostics output is indented with 2 spaces. - terminal.write(" \("\(verticallyAlignedLineNumber) | ", color: "[36m")\(highlightedCode)\n") // 36: cyan + terminal.write(" \(verticallyAlignedLineNumber) | ", inColor: .cyan) + terminal.write(" \(highlightedCode)\n") terminal.write(" \(String(repeating: " ", count: minimumSizeForLineNumbering)) | ", inColor: .cyan) // Aggregate the indicators (^ point to the error) onto a single line - var charIndicators = String(repeating: " ", count: Int(message.char)!) + "^" + + // Remove leading whitespace. + var charIndicators = String(repeating: " ", count: Int(message.char)! - (sourceLine.count - sourceLineSansWhitespace.count)) + "^" if allChars.count > 0 { for char in allChars.dropFirst() { let idx = Int(char)! if idx >= charIndicators.count { - charIndicators - .append(String(repeating: " ", count: idx - charIndicators.count) + "^") + for index in charIndicators.count..