Repository: cpisciotta/xcbeautify
Pull request #378 (2025-02-11 02:54:04)

Update Regex Pattern Matching
- Enforce case sensitivity
- Enforce regex anchors
- Find first match instead of all matches


Patch:
diff --git a/Sources/XcbeautifyLib/CaptureGroups.swift b/Sources/XcbeautifyLib/CaptureGroups.swift
index c6d4386..056b07a 100644
--- a/Sources/XcbeautifyLib/CaptureGroups.swift
+++ b/Sources/XcbeautifyLib/CaptureGroups.swift
@@ -779,7 +779,7 @@ struct GenerateCoverageDataCaptureGroup: CaptureGroup {
 
     /// Regular expression captured groups:
     /// $1 = coverage report file path
-    static let regex = XCRegex(pattern: #"^generating\s+coverage\s+data\.*"#)
+    static let regex = XCRegex(pattern: #"^Generating\s+coverage\s+data\.*$"#)
 
     private init() { }
 
@@ -791,7 +791,7 @@ struct GenerateCoverageDataCaptureGroup: CaptureGroup {
 
 struct GeneratedCoverageReportCaptureGroup: CaptureGroup {
     static let outputType: OutputType = .task
-    static let regex = XCRegex(pattern: #"^generated\s+coverage\s+report:\s+(.+)"#)
+    static let regex = XCRegex(pattern: #"^Generated\s+coverage\s+report:\s+(.+)"#)
 
     let coverageReportFilePath: String
 
@@ -1139,7 +1139,7 @@ struct ParallelTestSuiteStartedCaptureGroup: CaptureGroup {
     /// Regular expression captured groups:
     /// $1 = suite
     /// $2 = device
-    static let regex = XCRegex(pattern: #"^\s*Test\s+Suite\s+'(.*)'\s+started\s+on\s+'(.*)'"#)
+    static let regex = XCRegex(pattern: #"^\s*Test\s+suite\s+'(.*)'\s+started\s+on\s+'(.*)'"#)
 
     let suite: String
     let device: String
@@ -1335,7 +1335,7 @@ struct RegisterExecutionPolicyExceptionCaptureGroup: CaptureGroup {
 struct ScanDependenciesCaptureGroup: CaptureGroup {
     static let outputType: OutputType = .task
 
-    static let regex = XCRegex(pattern: #"ScanDependencies .* normal (arm64|arm64_32|armv7k|i386|x86_64) .* \(in target '(.*)' from project '(.*)'\)$"#)
+    static let regex = XCRegex(pattern: #"^ScanDependencies .* normal (arm64|arm64_32|armv7k|i386|x86_64) .* \(in target '(.*)' from project '(.*)'\)$"#)
 
     let arch: Architecture
     let target: String
@@ -1866,7 +1866,7 @@ struct SymbolReferencedFromCaptureGroup: CaptureGroup {
     /// Regular expression captured groups:
     /// $1 = wholeError
     /// $2 = reference
-    static let regex = XCRegex(pattern: #"(\s+\"(.*)\", referenced from:)$"#)
+    static let regex = XCRegex(pattern: #"^(\s+\"(.*)\", referenced from:)$"#)
 
     let wholeError: String
     let reference: String
@@ -1901,7 +1901,7 @@ struct UndefinedSymbolLocationCaptureGroup: CaptureGroup {
     /// $1 = whole warning
     /// $2 = target
     /// $3 = filename
-    static let regex = XCRegex(pattern: #"(.+ in (.+)\((.+)\.o\))$"#)
+    static let regex = XCRegex(pattern: #"^(.+ in (.+)\((.+)\.o\))$"#)
 
     let wholeWarning: String
     let target: String
@@ -2031,7 +2031,7 @@ struct CompilationResultCaptureGroup: CaptureGroup {
 struct SwiftDriverJobDiscoveryEmittingModuleCaptureGroup: CaptureGroup {
     static let outputType: OutputType = .task
 
-    static let regex = XCRegex(pattern: #"SwiftDriverJobDiscovery \w+ \w+ Emitting module for .* \(in target '.*' from project '.*'\)"#)
+    static let regex = XCRegex(pattern: #"^SwiftDriverJobDiscovery \w+ \w+ Emitting module for .* \(in target '.*' from project '.*'\)"#)
 
     init?(groups: [String]) { }
 }
diff --git a/Sources/XcbeautifyLib/Renderers/OutputRendering.swift b/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
index e2bad84..a6f8669 100644
--- a/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
+++ b/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
@@ -136,7 +136,7 @@ extension OutputRendering {
     }
 
     func formatCheckDependencies() -> String {
-        colored ? "Check Dependencies".style.Bold : "Check Dependencies"
+        colored ? "Check dependencies".style.Bold : "Check dependencies"
     }
 
     func formatCleanRemove(group: CleanRemoveCaptureGroup) -> String {
diff --git a/Sources/XcbeautifyLib/XCRegex.swift b/Sources/XcbeautifyLib/XCRegex.swift
index 822c0a3..189b91d 100644
--- a/Sources/XcbeautifyLib/XCRegex.swift
+++ b/Sources/XcbeautifyLib/XCRegex.swift
@@ -15,7 +15,7 @@ package final class XCRegex: @unchecked Sendable {
     private let pattern: String
 
     private lazy var regex: NSRegularExpression? = {
-        let regex = try? NSRegularExpression(pattern: "^" + pattern, options: [.caseInsensitive])
+        let regex = try? NSRegularExpression(pattern: pattern, options: [.anchorsMatchLines])
         assert(regex != nil)
         return regex
     }()
@@ -27,10 +27,7 @@ package final class XCRegex: @unchecked Sendable {
     func captureGroups(for line: String) -> [String]? {
         assert(regex != nil)
 
-        guard
-            let matches = regex?.matches(in: line, range: NSRange(location: 0, length: line.utf16.count)),
-            let match = matches.first
-        else {
+        guard let match = regex?.firstMatch(in: line, options: .anchored, range: NSRange(location: 0, length: line.utf16.count)) else {
             return nil
         }
 


Test patch:
diff --git a/Tests/XcbeautifyLibTests/ParserTests.swift b/Tests/XcbeautifyLibTests/ParserTests.swift
index 96cdd67..e1abab9 100644
--- a/Tests/XcbeautifyLibTests/ParserTests.swift
+++ b/Tests/XcbeautifyLibTests/ParserTests.swift
@@ -384,13 +384,9 @@ final class ParserTests: XCTestCase {
     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 {
@@ -400,6 +396,22 @@ final class ParserTests: XCTestCase {
         }
     }
 
+    // Note Output expects a lowercase n.
+    func testNegativeMatchNote() {
+        let inputs = [
+            #"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)"#,
+        ]
+
+        XCTAssertTrue(
+            inputs.allSatisfy { input in
+                parser.parse(line: input) == nil
+            }
+        )
+    }
+
     func testNotMatchNote() throws {
         let inputs = [
             "in the note middle",
diff --git a/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift
index 99a194f..8b2df1d 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/AzureDevOpsPipelinesRendererTests.swift
@@ -56,7 +56,7 @@ final class AzureDevOpsPipelinesRendererTests: XCTestCase {
     func testCheckDependenciesErrors() { }
 
     func testCheckDependencies() {
-        let command = "Check Dependencies"
+        let command = "Check dependencies"
         let formatted = logFormatted(command)
         XCTAssertEqual(formatted, command)
     }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
index 15b9e4e..8cbec3a 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
@@ -54,7 +54,7 @@ final class GitHubActionsRendererTests: XCTestCase {
     func testCheckDependenciesErrors() { }
 
     func testCheckDependencies() {
-        let command = "Check Dependencies"
+        let command = "Check dependencies"
         let formatted = logFormatted(command)
         XCTAssertEqual(formatted, command)
     }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
index 0904915..d31311a 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
@@ -54,7 +54,7 @@ final class TeamCityRendererTests: XCTestCase {
     func testCheckDependenciesErrors() { }
 
     func testCheckDependencies() {
-        let command = "Check Dependencies"
+        let command = "Check dependencies"
         let formatted = noColoredFormatted(command)
         XCTAssertEqual(formatted, command)
     }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
index a0b42f6..ec8d71a 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
@@ -54,7 +54,7 @@ final class TerminalRendererTests: XCTestCase {
     func testCheckDependenciesErrors() { }
 
     func testCheckDependencies() {
-        let command = "Check Dependencies"
+        let command = "Check dependencies"
         let formatted = noColoredFormatted(command)
         XCTAssertEqual(formatted, command)
     }