Skip to content

Commit

Permalink
Merge pull request #11 from surfstudio/SNP-1649-url-macro
Browse files Browse the repository at this point in the history
  • Loading branch information
NullIsOne authored Jul 2, 2024
2 parents 0e268d3 + a2577e8 commit 0146618
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Unit Tests

on: [push]

jobs:
test:
runs-on: macos-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Swift
uses: swift-actions/setup-swift@v2
with:
swift-version: '5.9'

- name: Build
run: swift build

- name: Test
run: swift test
1 change: 1 addition & 0 deletions Sources/SurfMacros/Implementation/MacrosPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import SwiftSyntaxMacros
struct MacrosPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
SignalsPlugin.providingMacros,
UtilsPlugin.providingMacros,
InfrastructurePlugin.providingMacros,
FactoryPlugin.providingMacros,
RouterPlugin.providingMacros
Expand Down
77 changes: 77 additions & 0 deletions Sources/SurfMacros/Implementation/Utils/URLMacro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Foundation
import SurfMacrosSupport
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros

public enum URLMacro: ExpressionMacro {

// MARK: - Names

private enum Names {
static let url = "URL"
static let argument = "string"
}

public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
let (argument, value) = try getInputArgument(from: node)
try checkURLString(value, from: argument)
return .init(createURLExpression(with: argument))
}

}

// MARK: - Getters

private extension URLMacro {

static func getInputArgument(
from node: some FreestandingMacroExpansionSyntax
) throws -> (argument: ExprSyntax, argumentValue: String) {
guard
let argument = node.argumentList.first?.expression,
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
segments.count == 1,
case .stringSegment(let literalSegment)? = segments.first
else {
throw CustomError(description: "#URL requires a static string literal")
}
return (argument: argument, argumentValue: literalSegment.content.text)
}

}

// MARK: - Checks

private extension URLMacro {

static func checkURLString(_ urlString: String, from argument: ExprSyntax) throws {
guard let _ = URL(string: urlString) else {
throw CustomError(description: "malformed url: \(argument)")
}
}

}

// MARK: - Creations

private extension URLMacro {

static func createURLExpression(with argument: ExprSyntax) -> ExprSyntaxProtocol {
let urlInit = DeclReferenceExprSyntax(baseName: .identifier(Names.url))
let stringArgument = LabeledExprSyntax(
label: .identifier(Names.argument),
expression: argument
)
let functionCall = FunctionCallExprSyntax(
calledExpression: urlInit,
arguments: [stringArgument]
)
return ForceUnwrapExprSyntax(expression: functionCall)
}

}
10 changes: 10 additions & 0 deletions Sources/SurfMacros/Implementation/Utils/UtilsPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros

struct UtilsPlugin {
static let providingMacros: [Macro.Type] = [
URLMacro.self
]
}
6 changes: 6 additions & 0 deletions Sources/SurfMacros/Macros/Utils/URL.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

/// Check if provided string literal is a valid URL and produce a non-optional
/// URL value. Emit error otherwise.
@freestanding(expression)
public macro URL(_ stringLiteral: String) -> URL = #externalMacro(module: "SurfMacroBody", type: "URLMacro")
18 changes: 18 additions & 0 deletions Sources/SurfMacros/Support/Library/Errors/CustomError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// CustomError.swift
//
//
// Created by pavlov on 30.06.2024.
//

import Foundation

public struct CustomError: Error, CustomStringConvertible {

public let description: String

public init(description: String) {
self.description = description
}

}
54 changes: 54 additions & 0 deletions Tests/SurfMacros/Utils/URLTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import XCTest

#if canImport(SurfMacroBody)
import SurfMacroBody

private let testMacros: [String: Macro.Type] = ["URL": URLMacro.self]
#endif

final class URLMacroTests: XCTestCase {
func testExpansionWithMalformedURLEmitsError() {
assertMacroExpansion(
"""
let invalid = #URL("https://not a url.com")
""",
expandedSource: """
let invalid = #URL("https://not a url.com")
""",
diagnostics: [
.init(message: #"malformed url: "https://not a url.com""#, line: 1, column: 15, severity: .error)
],
macros: testMacros
)
}

func testExpansionWithStringInterpolationEmitsError() {
assertMacroExpansion(
#"""
#URL("https://\(domain)/api/path")
"""#,
expandedSource: #"""
#URL("https://\(domain)/api/path")
"""#,
diagnostics: [
.init(message: "#URL requires a static string literal", line: 1, column: 1, severity: .error)
],
macros: testMacros
)
}

func testExpansionWithValidURL() {
assertMacroExpansion(
"""
let valid = #URL("https://swift.org/")
""",
expandedSource: """
let valid = URL(string: "https://swift.org/")!
""",
macros: testMacros
)
}

}

0 comments on commit 0146618

Please sign in to comment.