Skip to content

Migrate code to use Swift Regex #6598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Sources/Build/BuildOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import Foundation
import class TSCBasic.DiagnosticsEngine
import protocol TSCBasic.OutputByteStream
import class Basics.AsyncProcess
import struct TSCBasic.RegEx

import enum TSCUtility.Diagnostics

Expand Down Expand Up @@ -834,7 +833,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
guard let _ = self._buildPlan?.targets.first(where: { $0.module.name == target }) else { return nil }

// Check for cases involving modules that cannot be found.
if let importedModule = try? RegEx(pattern: "no such module '(.+)'").matchGroups(in: message).first?.first {
if let importedModule = message.firstMatch(of: #/no such module '(?<module>.+)'/#)?.module {
// A target is importing a module that can't be found. We take a look at the build plan and see if can offer any advice.

// Look for a target with the same module name as the one that's being imported.
Expand Down
13 changes: 7 additions & 6 deletions Sources/Build/LLBuildProgressTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import SPMBuildCore
import SPMLLBuild

import protocol TSCBasic.OutputByteStream
import struct TSCBasic.RegEx
import class TSCBasic.ThreadSafeOutputByteStream

import class TSCUtility.IndexStoreAPI
Expand Down Expand Up @@ -448,11 +447,13 @@ final class LLBuildProgressTracker: LLBuildBuildSystemDelegate, SwiftCompilerOut
// next we want to try and scoop out any errors from the output (if reasonable size, otherwise this
// will be very slow), so they can later be passed to the advice provider in case of failure.
if output.utf8.count < 1024 * 10 {
let regex = try! RegEx(pattern: #".*(error:[^\n]*)\n.*"#, options: .dotMatchesLineSeparators)
for match in regex.matchGroups(in: output) {
self.errorMessagesByTarget[parser.targetName] = (
self.errorMessagesByTarget[parser.targetName] ?? []
) + [match[0]]
let regex = #/.*(?<error>error:[^\n]*)\n.*/#.dotMatchesNewlines()
for match in output.matches(of: regex) {
self
.errorMessagesByTarget[parser.targetName] = (
self
.errorMessagesByTarget[parser.targetName] ?? []
) + [String(match.error)]
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,23 +234,11 @@ struct GitHubPackageMetadataProvider: PackageMetadataProvider, Closable {

// FIXME: use URL instead of string
internal static func apiURL(_ url: String) -> URL? {
do {
let regex = try NSRegularExpression(pattern: #"([^/@]+)[:/]([^:/]+)/([^/.]+)(\.git)?$"#, options: .caseInsensitive)
if let match = regex.firstMatch(in: url, options: [], range: NSRange(location: 0, length: url.count)) {
if let hostRange = Range(match.range(at: 1), in: url),
let ownerRange = Range(match.range(at: 2), in: url),
let repoRange = Range(match.range(at: 3), in: url) {
let host = String(url[hostRange])
let owner = String(url[ownerRange])
let repo = String(url[repoRange])

return URL(string: "https://\(Self.apiHostPrefix)\(host)/repos/\(owner)/\(repo)")
}
}
return nil
} catch {
let regex = #/(?<host>[^/@]+)[:/](?<owner>[^:/]+)/(?<repo>[^/.]+)(\.git)?$/#.ignoresCase()
guard let match = url.firstMatch(of: regex) else {
return nil
}
return URL(string: "https://\(Self.apiHostPrefix)\(match.host)/repos/\(match.owner)/\(match.repo)")
}

private func makeRequestOptions(validResponseCodes: [Int]) -> LegacyHTTPClientRequest.Options {
Expand Down
5 changes: 2 additions & 3 deletions Sources/PackageLoading/ManifestJSONParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import struct Basics.InternalError
import struct Basics.RelativePath

import enum TSCBasic.PathValidationError
import struct TSCBasic.RegEx
import struct TSCBasic.StringError

import struct TSCUtility.Version
Expand Down Expand Up @@ -254,7 +253,7 @@ enum ManifestJSONParser {
}

/// Looks for Xcode-style build setting macros "$()".
fileprivate static let invalidValueRegex = try! RegEx(pattern: #"(\$\(.*?\))"#)
fileprivate static let invalidValueRegex = #/(\$\(.*?\))/#
}

extension SystemPackageProviderDescription {
Expand Down Expand Up @@ -488,7 +487,7 @@ extension TargetBuildSettingDescription.Kind {
static func from(_ name: String, values: [String]) throws -> Self {
// Diagnose invalid values.
for item in values {
let groups = ManifestJSONParser.invalidValueRegex.matchGroups(in: item).flatMap{ $0 }
let groups = item.matches(of: ManifestJSONParser.invalidValueRegex).map { $0.1 }
if !groups.isEmpty {
let error = "the build setting '\(name)' contains invalid component(s): \(groups.joined(separator: " "))"
throw ManifestParseError.runtimeManifestErrors([error])
Expand Down
7 changes: 3 additions & 4 deletions Sources/PackageLoading/Target+PkgConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import Basics
import PackageModel

import class Basics.AsyncProcess
import struct TSCBasic.RegEx

import enum TSCUtility.Platform

Expand Down Expand Up @@ -318,14 +317,14 @@ public func removeDefaultFlags(cFlags: [String], libs: [String]) throws -> ([Str
///
/// See https://github.com/swiftlang/swift-package-manager/issues/6439 for details.
public func patchSDKPaths(in flags: [String], to sdkRootPath: AbsolutePath) throws -> [String] {
let sdkRegex = try! RegEx(pattern: #"^.*\.sdk(\/.*|$)"#)
let sdkRegex = #/^.*\.sdk(\/.*|$)/#

return try ["-I", "-L"].reduce(flags) { (flags, flag) in
try patch(flag: flag, in: flags) { value in
guard let groups = sdkRegex.matchGroups(in: value).first else {
guard let match = value.firstMatch(of: sdkRegex) else {
return value
}
return sdkRootPath.pathString + groups[0]
return sdkRootPath.pathString + match.1
}
}
}
Expand Down
27 changes: 17 additions & 10 deletions Sources/PackageLoading/ToolsVersionParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import Foundation
import PackageModel

import struct TSCBasic.ByteString
import struct TSCBasic.RegEx

import struct TSCUtility.Version

Expand Down Expand Up @@ -621,19 +620,27 @@ extension ManifestLoader {
do { contents = try fileSystem.getDirectoryContents(packagePath) } catch {
throw ToolsVersionParser.Error.inaccessiblePackage(path: packagePath, reason: String(describing: error))
}
let regex = try! RegEx(pattern: #"^Package@swift-(\d+)(?:\.(\d+))?(?:\.(\d+))?.swift$"#)

let regex = #/^Package@swift-(?<major>\d+)(?:\.(?<minor>\d+))?(?:\.(?<patch>\d+))?.swift$/#
// Collect all version-specific manifests at the given package path.
let versionSpecificManifests = Dictionary(contents.compactMap{ file -> (ToolsVersion, String)? in
let parsedVersion = regex.matchGroups(in: file)
guard parsedVersion.count == 1, parsedVersion[0].count == 3 else {
let parsedVersionMatches = file.matches(of: regex)
guard parsedVersionMatches.count == 1,
let parsedVersion = parsedVersionMatches.first else {
return nil
}

let major = Int(parsedVersion[0][0])!
let minor = parsedVersion[0][1].isEmpty ? 0 : Int(parsedVersion[0][1])!
let patch = parsedVersion[0][2].isEmpty ? 0 : Int(parsedVersion[0][2])!

let major = Int(parsedVersion.major) ?? 0
let minor: Int
if let minorString = parsedVersion.minor {
minor = Int(minorString) ?? 0
} else {
minor = 0
}
let patch: Int
if let patchString = parsedVersion.patch {
patch = Int(patchString) ?? 0
} else {
patch = 0
}
return (ToolsVersion(version: Version(major, minor, patch)), file)
}, uniquingKeysWith: { $1 })

Expand Down
26 changes: 17 additions & 9 deletions Sources/PackageModel/SwiftLanguageVersion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@

import Foundation

import struct TSCBasic.RegEx

import struct TSCUtility.Version

/// Represents a Swift language version.
Expand Down Expand Up @@ -63,21 +61,31 @@ public struct SwiftLanguageVersion: Hashable, Sendable {
}

/// Regex for parsing the Swift language version.
private static let regex = try! RegEx(pattern: #"^(\d+)(?:\.(\d+))?(?:\.(\d+))?$"#)
private static let regex = #/^(?<major>\d+)(?:\.(?<minor>\d+))?(?:\.(?<patch>\d+))?$/#

/// Create an instance of Swift language version from the given string.
///
// The Swift language version is not officially fixed but we require it to
// be a valid SemVer-like string.
public init?(string: String) {
let parsedVersion = SwiftLanguageVersion.regex.matchGroups(in: string)
guard parsedVersion.count == 1, parsedVersion[0].count == 3 else {
let parsedVersions = string.matches(of: SwiftLanguageVersion.regex)
guard parsedVersions.count == 1 else {
return nil
}
let major = Int(parsedVersion[0][0])!
let minor = parsedVersion[0][1].isEmpty ? 0 : Int(parsedVersion[0][1])!
let patch = parsedVersion[0][2].isEmpty ? 0 : Int(parsedVersion[0][2])!

let parsedVersion = parsedVersions[0]
let major = Int(parsedVersion.major) ?? 0
let minor: Int
if let minorString = parsedVersion.minor {
minor = Int(minorString) ?? 0
} else {
minor = 0
}
let patch: Int
if let patchString = parsedVersion.patch {
patch = Int(patchString) ?? 0
} else {
patch = 0
}
self.rawValue = string
self._version = Version(major, minor, patch)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,9 +437,9 @@ enum PackageArchiveSigner {
}
manifests.append(Manifest.filename)

let regex = try RegEx(pattern: #"^Package@swift-(\d+)(?:\.(\d+))?(?:\.(\d+))?.swift$"#)
let regex = #/^Package@swift-(\d+)(?:\.(\d+))?(?:\.(\d+))?.swift$/#
let versionSpecificManifests: [String] = packageContents.filter { file in
let matchGroups = regex.matchGroups(in: file)
let matchGroups = file.matches(of: regex)
return !matchGroups.isEmpty
}
manifests.append(contentsOf: versionSpecificManifests)
Expand Down
67 changes: 30 additions & 37 deletions Sources/SourceControl/GitRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import enum TSCBasic.FileMode
import struct TSCBasic.FileSystemError
import class Basics.AsyncProcess
import struct Basics.AsyncProcessResult
import struct TSCBasic.RegEx

import protocol TSCUtility.DiagnosticLocationProviding
import enum TSCUtility.Git
Expand Down Expand Up @@ -1243,7 +1242,7 @@ public enum GitProgressParser: FetchProgress {
case resolvingDeltas(progress: Double, currentObjects: Int, totalObjects: Int)

/// The pattern used to match git output. Capture groups are labeled from ?<i0> to ?<i19>.
static let pattern = #"""
private static let regex = #/
(?xi)
(?:
remote: \h+ (?<i0>Enumerating \h objects): \h+ (?<i1>[0-9]+)
Expand All @@ -1261,66 +1260,60 @@ public enum GitProgressParser: FetchProgress {
(?<i14>Receiving \h objects): \h+ (?<i15>[0-9]+)% \h+ \((?<i16>[0-9]+)\/(?<i17>[0-9]+)\)
(?:, \h+ (?<i18>[0-9]+.?[0-9]+ \h [A-Z]iB) \h+ \| \h+ (?<i19>[0-9]+.?[0-9]+ \h [A-Z]iB\/s))?
)
"""#
static let regex = try? RegEx(pattern: pattern)
/#

init?(from string: String) {
guard let matches = GitProgressParser.regex?.matchGroups(in: string).first,
matches.count == 20 else { return nil }

if matches[0] == "Enumerating objects" {
guard let currentObjects = Int(matches[1]) else { return nil }

guard let match = try? Self.regex.firstMatch(in: string)
else { return nil }
let captures = match.output
if captures.i0 == "Enumerating objects" {
guard let currentObjects = captures.i1.flatMap({ Int($0) })
else { return nil }
self = .enumeratingObjects(currentObjects: currentObjects)
} else if matches[2] == "Counting objects" {
guard let progress = Double(matches[3]),
let currentObjects = Int(matches[4]),
let totalObjects = Int(matches[5]) else { return nil }

} else if captures.i2 == "Counting objects" {
guard let progress = captures.i3.flatMap({ Double($0) }),
let currentObjects = captures.i4.flatMap({ Int($0) }),
let totalObjects = captures.i5.flatMap({ Int($0) })
else { return nil }
self = .countingObjects(
progress: progress / 100,
currentObjects: currentObjects,
totalObjects: totalObjects
)

} else if matches[6] == "Compressing objects" {
guard let progress = Double(matches[7]),
let currentObjects = Int(matches[8]),
let totalObjects = Int(matches[9]) else { return nil }

} else if captures.i6 == "Compressing objects" {
guard let progress = captures.i7.flatMap({ Double($0) }),
let currentObjects = captures.i8.flatMap({ Int($0) }),
let totalObjects = captures.i9.flatMap({ Int($0) })
else { return nil }
self = .compressingObjects(
progress: progress / 100,
currentObjects: currentObjects,
totalObjects: totalObjects
)

} else if matches[10] == "Resolving deltas" {
guard let progress = Double(matches[11]),
let currentObjects = Int(matches[12]),
let totalObjects = Int(matches[13]) else { return nil }

} else if captures.i10 == "Resolving deltas" {
guard let progress = captures.i11.flatMap({ Double($0) }),
let currentObjects = captures.i12.flatMap({ Int($0) }),
let totalObjects = captures.i13.flatMap({ Int($0) })
else { return nil }
self = .resolvingDeltas(
progress: progress / 100,
currentObjects: currentObjects,
totalObjects: totalObjects
)

} else if matches[14] == "Receiving objects" {
guard let progress = Double(matches[15]),
let currentObjects = Int(matches[16]),
let totalObjects = Int(matches[17]) else { return nil }

let downloadProgress = matches[18]
let downloadSpeed = matches[19]

} else if captures.i14 == "Receiving objects" {
guard let progress = captures.i15.flatMap({ Double($0) }),
let currentObjects = captures.i16.flatMap({ Int($0) }),
let totalObjects = captures.i17.flatMap({ Int($0) })
else { return nil }
let downloadProgress = captures.i18.map(String.init)
let downloadSpeed = captures.i19.map(String.init)
self = .receivingObjects(
progress: progress / 100,
currentObjects: currentObjects,
totalObjects: totalObjects,
downloadProgress: downloadProgress,
downloadSpeed: downloadSpeed
)

} else {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import PackageLoading
import PackageModel
import SourceControl

import struct TSCBasic.RegEx

import enum TSCUtility.Git
import struct TSCUtility.Version

Expand Down Expand Up @@ -280,8 +278,8 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri
)
} else {
// Revision does not exist, so we customize the error.
let sha1RegEx = try! RegEx(pattern: #"\A[:xdigit:]{40}\Z"#)
let isBranchRev = sha1RegEx.matchGroups(in: revision).compactMap { $0 }.isEmpty
let sha1RegEx = #/\A[:xdigit:]{40}\Z/#
let isBranchRev = revision.matches(of: sha1RegEx).isEmpty
let errorMessage = "could not find " + (isBranchRev ? "a branch named ‘\(revision)’" : "the commit \(revision)")
let mainBranchExists = (try? repository.resolveRevision(identifier: "main")) != nil
let suggestion = (revision == "master" && mainBranchExists) ? "did you mean ‘main’?" : nil
Expand Down