Repository: cpisciotta/xcbeautify (swift)

Issue:
[New Output] Support Note Output
### What's the uncaptured output?

```
note: Building targets in dependency order
```
```
note: Metadata extraction skipped. No AppIntents.framework dependency found. (in target 'Target' from project 'Project')
```
```
note: Run script build phase 'SomeRunScriptBuildPhase' will be run during every build because the option to run the script phase "Based on dependency analysis" is unchecked. (in target 'Target' from project 'Project')
```
```
note: Target dependency graph (12 targets)
```

### Suggested Regex

_No response_

### Suggested Output

_No response_

### Additional Context

_No response_

Pull request description:
Adresses #208.

### Changes

Add a capture group for the Note output.

### Testing

- Added new unit tests

Patch:
diff --git a/Sources/XcbeautifyLib/CaptureGroups.swift b/Sources/XcbeautifyLib/CaptureGroups.swift
index 5d99d34e..45b21505 100644
--- a/Sources/XcbeautifyLib/CaptureGroups.swift
+++ b/Sources/XcbeautifyLib/CaptureGroups.swift
@@ -2399,3 +2399,21 @@ struct EmitSwiftModuleCaptureGroup: CaptureGroup {
         self.project = project
     }
 }
+
+/// This output is printed when running
+/// `xcodebuild test -scheme xcbeautify-Package -destination 'platform=macOS,arch=arm64'`.
+struct NoteCaptureGroup: CaptureGroup {
+    static let outputType: OutputType = .task
+
+    /// Regular expression captured groups:
+    /// $1 = note
+    static let regex = XCRegex(pattern: "^note: (.*)$")
+
+    let note: String
+
+    init?(groups: [String]) {
+        assert(groups.count == 1)
+        guard let note = groups[safe: 0] else { return nil }
+        self.note = note
+    }
+}
diff --git a/Sources/XcbeautifyLib/Formatter.swift b/Sources/XcbeautifyLib/Formatter.swift
index a3c6ee51..8161dad6 100644
--- a/Sources/XcbeautifyLib/Formatter.swift
+++ b/Sources/XcbeautifyLib/Formatter.swift
@@ -272,6 +272,8 @@ package struct Formatter {
             return renderer.formatSwiftDriverCompilationRequirements(group: group)
         case let group as MkDirCaptureGroup:
             return renderer.formatMkDirCaptureGroup(group: group)
+        case let group as NoteCaptureGroup:
+            return renderer.formatNote(group: group)
         default:
             assertionFailure()
             return nil
diff --git a/Sources/XcbeautifyLib/Parser.swift b/Sources/XcbeautifyLib/Parser.swift
index b7ec5947..8956eec0 100644
--- a/Sources/XcbeautifyLib/Parser.swift
+++ b/Sources/XcbeautifyLib/Parser.swift
@@ -131,6 +131,7 @@ package final class Parser {
         SwiftDriverCompilationRequirementsCaptureGroup.self,
         MkDirCaptureGroup.self,
         NonPCHClangCommandCaptureGroup.self,
+        NoteCaptureGroup.self,
     ]
 
     // MARK: - Init
diff --git a/Sources/XcbeautifyLib/Renderers/OutputRendering.swift b/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
index beccebb2..f494e674 100644
--- a/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
+++ b/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
@@ -112,6 +112,7 @@ protocol OutputRendering {
     func formatSwiftTestingPassingArgument(group: SwiftTestingPassingArgumentCaptureGroup) -> String?
     func formatSwiftDriverCompilationTarget(group: SwiftDriverCompilationTarget) -> String?
     func formatMkDirCaptureGroup(group: MkDirCaptureGroup) -> String?
+    func formatNote(group: NoteCaptureGroup) -> String
 }
 
 extension OutputRendering {
@@ -727,4 +728,8 @@ extension OutputRendering {
     func formatMkDirCaptureGroup(group: MkDirCaptureGroup) -> String? {
         nil
     }
+
+    func formatNote(group: NoteCaptureGroup) -> String {
+        colored ? "note: ".s.Bold.f.Cyan + group.note : "note: " + group.note
+    }
 }


Test patch:
diff --git a/Tests/XcbeautifyLibTests/ParserTests.swift b/Tests/XcbeautifyLibTests/ParserTests.swift
index d90e44d0..b222c298 100644
--- a/Tests/XcbeautifyLibTests/ParserTests.swift
+++ b/Tests/XcbeautifyLibTests/ParserTests.swift
@@ -327,4 +327,39 @@ final class ParserTests: XCTestCase {
         XCTAssertEqual(captureGroup.target, "Target")
         XCTAssertEqual(captureGroup.project, "Project")
     }
+
+    func testMatchNote() throws {
+        let inputs = [
+            ("note:", "Building targets in dependency order"),
+            ("Note:", "Building targets in dependency order"),
+            ("note:", "Metadata extraction skipped. No AppIntents.framework dependency found. (in target 'Target' from project 'Project')"),
+            ("Note:", "Metadata extraction skipped. No AppIntents.framework dependency found. (in target 'Target' from project 'Project')"),
+            ("note:", #"Run script build phase 'SomeRunScriptBuildPhase' will be run during every build because the option to run the script phase "Based on dependency analysis" is unchecked. (in target 'Target' from project 'Project')"#),
+            ("Note:", #"Run script build phase 'SomeRunScriptBuildPhase' will be run during every build because the option to run the script phase "Based on dependency analysis" is unchecked. (in target 'Target' from project 'Project')"#),
+            ("note:", "Target dependency graph (12 targets)"),
+            ("Note:", "Target dependency graph (12 targets)"),
+        ]
+
+        for (keyword, note) in inputs {
+            let input = "\(keyword) \(note)"
+            let captureGroup = try XCTUnwrap(parser.parse(line: input) as? NoteCaptureGroup)
+            XCTAssertEqual(captureGroup.note, note)
+        }
+    }
+
+    func testNotMatchNote() throws {
+        let inputs = [
+            "in the note middle",
+            "in the note: middle",
+            "note Building targets in dependency order",
+            "Note Metadata extraction skipped.",
+            "Target dependency graph (12 targets) note",
+            "Target dependency graph (12 targets) note:",
+            "Target dependency graph (12 targets): note:",
+        ]
+
+        for input in inputs {
+            XCTAssertNil(parser.parse(line: input))
+        }
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/ParsingTests/ParsingTests.swift b/Tests/XcbeautifyLibTests/ParsingTests/ParsingTests.swift
index b1219fce..9b315de2 100644
--- a/Tests/XcbeautifyLibTests/ParsingTests/ParsingTests.swift
+++ b/Tests/XcbeautifyLibTests/ParsingTests/ParsingTests.swift
@@ -26,9 +26,9 @@ final class ParsingTests: XCTestCase {
         // Update this magic number whenever `uncapturedOutput` is less than the current magic number.
         // There's a regression whenever `uncapturedOutput` is greater than the current magic number.
         #if os(macOS)
-        XCTAssertEqual(uncapturedOutput, 78)
+        XCTAssertEqual(uncapturedOutput, 73)
         #else
-        XCTAssertEqual(uncapturedOutput, 94)
+        XCTAssertEqual(uncapturedOutput, 89)
         #endif
     }
 
@@ -56,9 +56,9 @@ final class ParsingTests: XCTestCase {
         // Update this magic number whenever `uncapturedOutput` is less than the current magic number.
         // There's a regression whenever `uncapturedOutput` is greater than the current magic number.
         #if os(macOS)
-        XCTAssertEqual(uncapturedOutput, 2588)
+        XCTAssertEqual(uncapturedOutput, 2428)
         #else
-        XCTAssertEqual(uncapturedOutput, 3156)
+        XCTAssertEqual(uncapturedOutput, 2996)
         #endif
     }
 
diff --git a/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift
index 1bc39062..b614fa75 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift
@@ -751,4 +751,9 @@ final class AzureDevOpsPipelinesRendererTests: XCTestCase {
         let input = "EmitSwiftModule normal arm64 (in target 'Target' from project 'Project')"
         XCTAssertNil(logFormatted(input))
     }
+
+    func testNote() {
+        let formatted = logFormatted("note: Building targets in dependency order")
+        XCTAssertEqual(formatted, "note: Building targets in dependency order")
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
index e463b20c..8bb7ba57 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
@@ -748,4 +748,9 @@ final class GitHubActionsRendererTests: XCTestCase {
         let input = "EmitSwiftModule normal arm64 (in target 'Target' from project 'Project')"
         XCTAssertNil(logFormatted(input))
     }
+
+    func testNote() {
+        let formatted = logFormatted("note: Building targets in dependency order")
+        XCTAssertEqual(formatted, "note: Building targets in dependency order")
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
index e00f55cb..cc9d87ed 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
@@ -753,4 +753,9 @@ final class TeamCityRendererTests: XCTestCase {
         let input = "EmitSwiftModule normal arm64 (in target 'Target' from project 'Project')"
         XCTAssertNil(noColoredFormatted(input))
     }
+
+    func testNote() {
+        let formatted = noColoredFormatted("note: Building targets in dependency order")
+        XCTAssertEqual(formatted, "note: Building targets in dependency order")
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
index 14c4bbcd..6ea210f5 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
@@ -785,4 +785,9 @@ final class TerminalRendererTests: XCTestCase {
         let input = "EmitSwiftModule normal arm64 (in target 'Target' from project 'Project')"
         XCTAssertNil(noColoredFormatted(input))
     }
+
+    func testNote() {
+        let formatted = noColoredFormatted("note: Building targets in dependency order")
+        XCTAssertEqual(formatted, "note: Building targets in dependency order")
+    }
 }