Skip to content
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

Tool - Support Swift 6 language mode (Strict Concurrency) #112

Merged
merged 13 commits into from
Aug 4, 2024
Merged
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