Skip to content

Commit

Permalink
Support iOS 15/macOS 12 (#50)
Browse files Browse the repository at this point in the history
* Update StringGenerator to produce APIs that work on WWDC21 OS releases

* Make implementation details of generated Localizable struct private

* Refactor generator and create SwiftSyntax extensions

* Update snapshots

* Make BundleDescription type fileprivate

* Restore documentation
  • Loading branch information
liamnichols authored Apr 12, 2024
1 parent 9488e2c commit 15acd61
Show file tree
Hide file tree
Showing 21 changed files with 2,394 additions and 557 deletions.
1,018 changes: 866 additions & 152 deletions Sources/StringGenerator/StringGenerator.swift

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ extension AvailabilityArgumentListSyntax {
AvailabilityArgumentSyntax(argument: .token(.binaryOperator("*")))
]
}

static var wwdc2021: AvailabilityArgumentListSyntax {
[
AvailabilityArgumentSyntax(argument: .platformVersionRestriction("macOS", versionMajor: 12))
.with(\.trailingComma, .commaToken()),

AvailabilityArgumentSyntax(argument: .platformVersionRestriction("iOS", versionMajor: 15))
.with(\.trailingComma, .commaToken()),

AvailabilityArgumentSyntax(argument: .platformVersionRestriction("tvOS", versionMajor: 15))
.with(\.trailingComma, .commaToken()),

AvailabilityArgumentSyntax(argument: .platformVersionRestriction("watchOS", versionMajor: 8))
.with(\.trailingComma, .commaToken()),

AvailabilityArgumentSyntax(argument: .token(.binaryOperator("*")))
]
}
}

extension PlatformVersionSyntax {
Expand Down
33 changes: 33 additions & 0 deletions Sources/StringGenerator/SwiftSyntax/ExtensionDeclSyntax.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import SwiftSyntax
import SwiftSyntaxBuilder

extension ExtensionDeclSyntax {
init(
availability: AvailabilityArgumentListSyntax,
accessLevel: Keyword? = nil,
extendedType: some TypeSyntaxProtocol,
@MemberBlockItemListBuilder memberBlockBuilder: () -> MemberBlockItemListSyntax
) {
self.init(
attributes: [
.attribute(
AttributeSyntax(availability: availability)
.with(\.trailingTrivia, .newline)
)
],
modifiers: DeclModifierListSyntax {
if let accessLevel {
DeclModifierSyntax(name: .keyword(accessLevel))
}
},
extendedType: extendedType,
memberBlock: MemberBlockSyntax(members: memberBlockBuilder())
)
}

func spacingMembers(_ lineCount: Int = 2) -> ExtensionDeclSyntax {
updating(\.memberBlock) { memberBlock in
memberBlock = memberBlock.spacingMembers(lineCount)
}
}
}
22 changes: 22 additions & 0 deletions Sources/StringGenerator/SwiftSyntax/FunctionCallExprSyntax.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import SwiftSyntax
import SwiftSyntaxBuilder

extension FunctionCallExprSyntax {
func multiline() -> FunctionCallExprSyntax {
self
.updating(\.arguments) { arguments in
arguments = LabeledExprListSyntax {
for (idx, argument) in zip(1..., arguments) {
if idx == arguments.count {
argument.with(\.leadingTrivia, .newline)
} else {
argument
.with(\.trailingComma, .commaToken())
.with(\.leadingTrivia, .newline)
}
}
}
}
.with(\.rightParen, .rightParenToken(leadingTrivia: .newline))
}
}
38 changes: 38 additions & 0 deletions Sources/StringGenerator/SwiftSyntax/FunctionSignatureSyntax.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import SwiftSyntax
import SwiftSyntaxBuilder

extension FunctionSignatureSyntax {
func multiline() -> FunctionSignatureSyntax {
self
.updating(\.parameterClause) { $0 = $0.multiline() }
}
}

extension FunctionParameterClauseSyntax {
func multiline() -> FunctionParameterClauseSyntax {
self
.updating(\.parameters) { parameters in
parameters = FunctionParameterListSyntax {
for parameter in parameters {
parameter.with(\.leadingTrivia, .newline)
}
}
}
.with(\.rightParen, .rightParenToken(leadingTrivia: .newline))
}

func commaSeparated() -> FunctionParameterClauseSyntax {
self.updating(\.parameters) { parameters in
parameters = FunctionParameterListSyntax {
for (idx, parameter) in zip(1..., parameters) {
if idx == parameters.count {
parameter
} else {
parameter
.with(\.trailingComma, .commaToken())
}
}
}
}
}
}
17 changes: 17 additions & 0 deletions Sources/StringGenerator/SwiftSyntax/MemberBlockSyntax.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import SwiftSyntax

extension MemberBlockSyntax {
func spacingMembers(_ lineCount: Int = 2) -> MemberBlockSyntax {
updating(\.members) { members in
members = MemberBlockItemListSyntax {
for (idx, item) in zip(1..., members) {
if idx == members.count {
item
} else {
item.with(\.trailingTrivia, .newlines(lineCount))
}
}
}
}
}
}
18 changes: 18 additions & 0 deletions Sources/StringGenerator/SwiftSyntax/SourceFileSyntax.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import SwiftSyntax

extension SourceFileSyntax {
func spacingStatements(_ lineCount: Int = 2) -> Self {
var copy = self
copy.statements = CodeBlockItemListSyntax {
for (idx, item) in zip(1..., statements) {
if idx == statements.count {
item
} else {
item.with(\.trailingTrivia, .newlines(lineCount))
}
}
}
return copy
}
}

9 changes: 9 additions & 0 deletions Sources/StringGenerator/SwiftSyntax/StructDeclSyntax.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import SwiftSyntax

extension StructDeclSyntax {
func spacingMembers(_ lineCount: Int = 2) -> StructDeclSyntax {
updating(\.memberBlock) { memberBlock in
memberBlock = memberBlock.spacingMembers(lineCount)
}
}
}
10 changes: 10 additions & 0 deletions Sources/StringGenerator/SwiftSyntax/SyntaxProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SwiftSyntax

public extension SyntaxProtocol {
/// Returns a new syntax node that has the child at `keyPath` updated by the changes closure
func updating<T>(_ keyPath: WritableKeyPath<Self, T>, changes: (inout T) -> Void) -> Self {
var copy = self
changes(&copy[keyPath: keyPath])
return copy
}
}
11 changes: 11 additions & 0 deletions Sources/StringGenerator/SwiftSyntax/TokenSyntax+Import.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import SwiftSyntax

extension TokenSyntax {
enum Import: String {
case Foundation = "Foundation"
}

static func `import`(_ value: Import) -> TokenSyntax {
.identifier(value.rawValue)
}
}
24 changes: 24 additions & 0 deletions Sources/StringGenerator/SwiftSyntax/TokenSyntax+Types.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import SwiftSyntax

extension TokenSyntax {
enum MetaType: String {
case String
case StaticString
case LocalizationValue
case Locale
case Bundle
case AnyClass
case BundleDescription
case LocalizedStringResource
}

static func type(_ value: MetaType) -> TokenSyntax {
.identifier(value.rawValue)
}
}

extension TypeSyntaxProtocol where Self == IdentifierTypeSyntax {
static func identifier(_ value: TokenSyntax.MetaType) -> IdentifierTypeSyntax {
IdentifierTypeSyntax(name: .type(value))
}
}
19 changes: 19 additions & 0 deletions Tests/PluginTests/PluginTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ final class PluginTests: XCTestCase {
String(localized: .localizable.demoBasic),
"A basic string"
)
XCTAssertEqual(
String(localizable: .demoBasic),
"A basic string"
)

XCTAssertEqual(
String(localized: .localizable.multiline(2)),
Expand All @@ -15,15 +19,30 @@ final class PluginTests: XCTestCase {
spans 2 lines
"""
)
XCTAssertEqual(
String(localizable: .multiline(2)),
"""
A string that
spans 2 lines
"""
)

XCTAssertEqual(
String(localized: .featureOne.pluralExample(1)),
"1 string remaining"
)
XCTAssertEqual(
String(featureOne: .pluralExample(1)),
"1 string remaining"
)

XCTAssertEqual(
String(localized: .featureOne.pluralExample(10)),
"10 strings remaining"
)
XCTAssertEqual(
String(featureOne: .pluralExample(10)),
"10 strings remaining"
)
}
}
Loading

0 comments on commit 15acd61

Please sign in to comment.