Skip to content

Commit

Permalink
Tool - Support Swift 6 language mode (Strict Concurrency) (#112)
Browse files Browse the repository at this point in the history
* Add @retroactive to surpress warnings from SE-0364

* Extract logging into Logger type instead of using global variables and methods

* Add Sendable conformance to StringCatalog types

* wip

* Update XCStringsToolTests to support Swift 6 language mode

* wip

* Revert support back to Swift 5.9 tools

* reenable BareSlashRegexLiterals

* Enable StrictConcurrency experimental feature

* Move Regex back into a static helper

* Fix use of StrictConcurrency

* Enable Swift 6 language mode via swiftLanguageVersions

* Stop using bare slash regex literals
  • Loading branch information
liamnichols authored Aug 4, 2024
1 parent e370ad9 commit a680cf5
Show file tree
Hide file tree
Showing 20 changed files with 144 additions and 174 deletions.
6 changes: 2 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ let package = Package(
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
.target(name: "XCStringsToolConstants")
],
swiftSettings: [
.enableUpcomingFeature("BareSlashRegexLiterals")
]
),

Expand Down Expand Up @@ -139,7 +136,8 @@ let package = Package(
.copy("__Fixtures__")
]
)
]
],
swiftLanguageVersions: [.v5, .version("6")]
)

// https://swiftpackageindex.com/swiftpackageindex/spimanifest/0.19.0/documentation/spimanifest/validation
Expand Down
2 changes: 1 addition & 1 deletion Sources/StringCatalog/Types/StringExtractionState.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public struct StringExtractionState: Codable, Hashable, RawRepresentable, ExpressibleByStringLiteral {
public struct StringExtractionState: Sendable, Codable, Hashable, RawRepresentable, ExpressibleByStringLiteral {
public let rawValue: String

public init(rawValue: String) {
Expand Down
2 changes: 1 addition & 1 deletion Sources/StringCatalog/Types/StringLanguage.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public struct StringLanguage: Codable, Hashable, RawRepresentable, ExpressibleByStringLiteral, CodingKeyRepresentable {
public struct StringLanguage: Sendable, Codable, Hashable, RawRepresentable, ExpressibleByStringLiteral, CodingKeyRepresentable {
public let rawValue: String

public init(rawValue: String) {
Expand Down
2 changes: 1 addition & 1 deletion Sources/StringCatalog/Types/StringUnitState.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public struct StringUnitState: Codable, Hashable, RawRepresentable, ExpressibleByStringLiteral {
public struct StringUnitState: Sendable, Codable, Hashable, RawRepresentable, ExpressibleByStringLiteral {
public let rawValue: String

public init(rawValue: String) {
Expand Down
4 changes: 2 additions & 2 deletions Sources/StringCatalog/Types/StringVariations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public struct StringVariations: Codable {
}

extension StringVariations {
public struct DeviceKey: Codable, Hashable, RawRepresentable, ExpressibleByStringLiteral, CodingKeyRepresentable {
public struct DeviceKey: Sendable, Codable, Hashable, RawRepresentable, ExpressibleByStringLiteral, CodingKeyRepresentable {
public let rawValue: String

public init(rawValue: String) {
Expand All @@ -32,7 +32,7 @@ extension StringVariations {
public static let other = Self(rawValue: "other")
}

public struct PluralKey: Codable, Hashable, RawRepresentable, ExpressibleByStringLiteral, CodingKeyRepresentable {
public struct PluralKey: Sendable, Codable, Hashable, RawRepresentable, ExpressibleByStringLiteral, CodingKeyRepresentable {
public let rawValue: String

public init(rawValue: String) {
Expand Down
4 changes: 2 additions & 2 deletions Sources/StringExtractor/ExtractionError.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

public enum ExtractionError: Error {
public struct Context {
public enum ExtractionError: Sendable, Error {
public struct Context: Sendable {
/// The key of the localization being parsed
public let key: String

Expand Down
86 changes: 43 additions & 43 deletions Sources/StringExtractor/StringParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct StringParser {
var segments: [ParsedSegment] = []
var lastIndex = input.startIndex

for match in input.matches(of: regex) {
for match in input.matches(of: Self.regex) {
// Create a segment from the previous bound to here
if match.range.lowerBound != lastIndex {
let string = String(input[lastIndex ..< match.range.lowerBound])
Expand Down Expand Up @@ -59,55 +59,55 @@ struct StringParser {
}

extension StringParser {
static let regex = Regex {
// The start of the specifier
"%"

// Optional, positional information
Optionally {
TryCapture {
OneOrMore(.digit)
} transform: { rawValue in
Int(rawValue)
static var regex: Regex<Regex<(Substring, Int?, String)>.RegexOutput> {
Regex {
// The start of the specifier
"%"

// Optional, positional information
Optionally {
TryCapture {
OneOrMore(.digit)
} transform: { rawValue in
Int(rawValue)
}
"$"
}
"$"
}

// Optional, precision information
Optionally(.anyOf("-+"))
Optionally(.digit)
Optionally {
"."
One(.digit)
}
// Optional, precision information
Optionally(.anyOf("-+"))
Optionally(.digit)
Optionally {
"."
One(.digit)
}

// Required, the type (inc lengths)
TryCapture {
ChoiceOf {
"%"
"@"
Regex {
Optionally {
ChoiceOf {
"h"
"hh"
"l"
"ll"
"q"
"z"
"t"
"j"
// Required, the type (inc lengths)
TryCapture {
ChoiceOf {
"%"
"@"
Regex {
Optionally {
ChoiceOf {
"h"
"hh"
"l"
"ll"
"q"
"z"
"t"
"j"
}
}
One(.anyOf("dioux"))
}
One(.anyOf("dioux"))
One(.anyOf("aefg"))
One(.anyOf("csp"))
}
One(.anyOf("aefg"))
One(.anyOf("csp"))
} transform: { rawValue in
String(rawValue)
}
} transform: { rawValue in
String(rawValue)
}
}
}


21 changes: 0 additions & 21 deletions Sources/StringGenerator/Extensions/Array+Position.swift

This file was deleted.

18 changes: 0 additions & 18 deletions Sources/StringGenerator/Extensions/EnumCaseElementSyntax.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Sources/StringGenerator/StringGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ private extension String {
// https://github.com/liamnichols/xcstrings-tool/issues/97
func patchingSwift6CompatibilityIssuesIfNeeded() -> String {
#if !canImport(SwiftSyntax600)
replacing(/(?:[#@]available|==)\s\(/, with: { match in
replacing(#/(?:[#@]available|==)\s\(/#, with: { match in
match.output.filter { !$0.isWhitespace }
})
#else
Expand Down
24 changes: 12 additions & 12 deletions Sources/xcstrings-tool/Generate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,56 +48,56 @@ struct Generate: ParsableCommand {
// MARK: - Program

func run() throws {
isVerboseLoggingEnabled = verbose
let logger = Logger(isVerboseLoggingEnabled: verbose)

// Parse the input from the invocation arguments
let input = try withThrownErrorsAsDiagnostics {
try InputParser.parse(from: inputs, developmentLanguage: resolvedDevelopmentLanguage)
try InputParser.parse(from: inputs, developmentLanguage: resolvedDevelopmentLanguage, logger: logger)
}

// Collect the results for each input file
let results = try input.files.map { input in
try withThrownErrorsAsDiagnostics(at: input) {
debug("collecting results for ‘\(input.absoluteURL.path())")
logger.debug("collecting results for ‘\(input.absoluteURL.path())")

// Load the source content
debug(" loading source file")
logger.debug(" loading source file")
let source = try StringSource(contentsOf: input)

// Extract any resources from this input
debug(" extracting resources")
logger.debug(" extracting resources")
let result = try StringExtractor.extractResources(from: source)

// Validate the extraction result
debug(" validating contents")
result.issues.forEach { warning($0.description, sourceFile: input) }
try ResourceValidator.validateResources(result.resources, in: input)
logger.debug(" validating contents")
result.issues.forEach { logger.warning($0.description, sourceFile: input) }
try ResourceValidator.validateResources(result.resources, in: input, logger: logger)

// Return the resources
return result
}
}

// Merge the resources together, ensure that they are uniquely keyed and sorted
let resources = try StringExtractor.mergeAndEnsureUnique(results)
let resources = try StringExtractor.mergeAndEnsureUnique(results, logger: logger)

// Generate the associated Swift source
debug("generating Swift source code")
logger.debug("generating Swift source code")
let source = StringGenerator.generateSource(
for: resources,
tableName: input.tableName,
accessLevel: resolvedAccessLevel
)

// Write the output and catch errors in a diagnostic format
debug("writing output")
logger.debug("writing output")
try withThrownErrorsAsDiagnostics {
// Create the directory if it doesn't exist
try createDirectoryIfNeeded(for: output)

// Write the source to disk
try source.write(to: output, atomically: false, encoding: .utf8)
note("Output written to ‘\(output.path(percentEncoded: false))")
logger.note("Output written to ‘\(output.path(percentEncoded: false))")
}
}

Expand Down
7 changes: 5 additions & 2 deletions Sources/xcstrings-tool/Utilities/AccessLevel+Resolution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,8 @@ extension AccessLevel {
}
}

extension AccessLevel: ExpressibleByArgument {
}
#if compiler(>=6.0)
extension AccessLevel: @retroactive ExpressibleByArgument {}
#else
extension AccessLevel: ExpressibleByArgument {}
#endif
66 changes: 39 additions & 27 deletions Sources/xcstrings-tool/Utilities/Diagnostics.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import Foundation

var isVerboseLoggingEnabled = false

struct Diagnostic: CustomStringConvertible {
enum Severity {
case error, warning, note
Expand Down Expand Up @@ -67,40 +65,54 @@ func withThrownErrorsAsDiagnostics<T>(
}
}

/// Log a diagnostic with the note severity only when in verbose mode
func debug(_ message: @autoclosure () -> String, sourceFile: URL? = nil) {
guard isVerboseLoggingEnabled else { return }
note(message(), sourceFile: sourceFile)
}
struct Logger: Sendable {
var isVerboseLoggingEnabled = false

/// Log a diagnostic with the note severity
func note(_ message: String, sourceFile: URL? = nil) {
log(.note, message, sourceFile: sourceFile)
}
/// Log a diagnostic with the note severity only when in verbose mode
func debug(_ message: @autoclosure () -> String, sourceFile: URL? = nil) {
guard isVerboseLoggingEnabled else { return }
note(message(), sourceFile: sourceFile)
}

/// Log a diagnostic with the warning severity
func warning(_ message: String, sourceFile: URL? = nil) {
log(.warning, message, sourceFile: sourceFile)
}
/// Log a diagnostic with the note severity
func note(_ message: String, sourceFile: URL? = nil) {
log(.note, message, sourceFile: sourceFile)
}

/// Log a diagnostic with the error severity
func error(_ message: String, sourceFile: URL? = nil) {
log(.error, message, sourceFile: sourceFile)
}
/// Log a diagnostic with the warning severity
func warning(_ message: String, sourceFile: URL? = nil) {
log(.warning, message, sourceFile: sourceFile)
}

func log(_ severity: Diagnostic.Severity, _ message: String, sourceFile: URL? = nil) {
print(
Diagnostic(
severity: severity,
sourceFile: sourceFile,
message: message
/// Log a diagnostic with the error severity
func error(_ message: String, sourceFile: URL? = nil) {
log(.error, message, sourceFile: sourceFile)
}

func log(_ severity: Diagnostic.Severity, _ message: String, sourceFile: URL? = nil) {
print(
Diagnostic(
severity: severity,
sourceFile: sourceFile,
message: message
)
)
)
}
}

// MARK: - Better Errors
#if compiler(>=6.0)
extension DecodingError: @retroactive CustomDebugStringConvertible {
public var debugDescription: String { _debugDescription }
}
#else
extension DecodingError: CustomDebugStringConvertible {
public var debugDescription: String {
public var debugDescription: String { _debugDescription }
}
#endif

private extension DecodingError {
var _debugDescription: String {
guard let context else { return localizedDescription }

if context.codingPath.isEmpty {
Expand Down
Loading

0 comments on commit a680cf5

Please sign in to comment.