-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add macro to perform compile-time template processing
- Loading branch information
Showing
6 changed files
with
278 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
Sources/ScreamURITemplateCompilerPlugin/URLByExpandingURITemplateMacro.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// Copyright 2018-2024 Alex Deem | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import Foundation | ||
import SwiftCompilerPlugin | ||
import SwiftDiagnostics | ||
import SwiftSyntax | ||
import SwiftSyntaxMacros | ||
|
||
import ScreamURITemplate | ||
|
||
public struct URLByExpandingURITemplateMacro: ExpressionMacro { | ||
public static func expansion( | ||
of node: some FreestandingMacroExpansionSyntax, | ||
in _: some MacroExpansionContext) throws -> ExprSyntax { | ||
guard let templateArgument = node.arguments.first?.expression, | ||
let uriTemplateString = templateArgument.stringLiteral() else { | ||
throw DiagnosticsError(diagnostics: [ | ||
Diagnostic(node: node, | ||
message: MacroExpansionErrorMessage("#URLByExpandingURITemplate requires a static string literal for the first argument")), | ||
]) | ||
} | ||
|
||
guard let paramsArgument = node.arguments.last?.expression, | ||
let params = paramsArgument.dictionaryLiteral() else { | ||
throw DiagnosticsError(diagnostics: [ | ||
Diagnostic(node: node, | ||
message: MacroExpansionErrorMessage("#URLByExpandingURITemplate requires a Dictionary Literal of string literals for the second argument")), | ||
]) | ||
} | ||
|
||
let template: URITemplate | ||
do { | ||
template = try URITemplate(string: uriTemplateString) | ||
} catch { | ||
throw DiagnosticsError(diagnostics: [ | ||
Diagnostic(node: templateArgument, | ||
message: MacroExpansionErrorMessage("Invalid URI template: \(error.reason) at \"\(uriTemplateString.suffix(from: error.position).prefix(50))\"")), | ||
]) | ||
} | ||
|
||
let processedTemplate: String | ||
do { | ||
processedTemplate = try template.process(variables: params) | ||
} catch { | ||
throw DiagnosticsError(diagnostics: [ | ||
Diagnostic(node: node, | ||
message: MacroExpansionErrorMessage("Failed to process template: \(error.reason)")), | ||
]) | ||
} | ||
|
||
guard URL(string: processedTemplate) != nil else { | ||
throw DiagnosticsError(diagnostics: [ | ||
Diagnostic(node: node, | ||
message: MacroExpansionErrorMessage("Processed template does not form a valid URL\n\(processedTemplate)")), | ||
]) | ||
} | ||
|
||
return "URL(string: \(processedTemplate.makeLiteralSyntax()))!" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
168 changes: 168 additions & 0 deletions
168
Tests/ScreamURITemplateTests/URLByExpandingURITemplateMacroTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// Copyright 2018-2024 Alex Deem | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#if canImport(ScreamURITemplateCompilerPlugin) | ||
import ScreamURITemplateCompilerPlugin | ||
|
||
import SwiftSyntaxMacros | ||
import SwiftSyntaxMacrosTestSupport | ||
|
||
import XCTest | ||
|
||
class URLByExpandingURITemplateMacroTests: XCTestCase { | ||
let testMacros: [String: Macro.Type] = [ | ||
"URLByExpandingURITemplate": URLByExpandingURITemplateMacro.self, | ||
] | ||
|
||
func testValid() throws { | ||
assertMacroExpansion( | ||
#""" | ||
#URLByExpandingURITemplate("https://api.github.com/repos/{owner}/{repo}/collaborators/{username}", [ | ||
"owner": "SwiftScream", | ||
"repo": "URITemplate", | ||
"username": "alexdeem", | ||
]) | ||
"""#, | ||
expandedSource: | ||
#""" | ||
URL(string: "https://api.github.com/repos/SwiftScream/URITemplate/collaborators/alexdeem")! | ||
"""#, | ||
diagnostics: [], | ||
macros: testMacros) | ||
} | ||
|
||
func testInvalidTemplate() throws { | ||
assertMacroExpansion( | ||
#""" | ||
#URLByExpandingURITemplate("https://api.github.com/repos/{}/{repo}/collaborators/{username}", [ | ||
"owner": "SwiftScream", | ||
"repo": "URITemplate", | ||
"username": "alexdeem", | ||
]) | ||
"""#, | ||
expandedSource: | ||
#""" | ||
#URLByExpandingURITemplate("https://api.github.com/repos/{}/{repo}/collaborators/{username}", [ | ||
"owner": "SwiftScream", | ||
"repo": "URITemplate", | ||
"username": "alexdeem", | ||
]) | ||
"""#, | ||
diagnostics: [ | ||
DiagnosticSpec(message: "Invalid URI template: Empty Variable Name at \"}/{repo}/collaborators/{username}\"", line: 1, column: 28), | ||
], | ||
macros: testMacros) | ||
} | ||
|
||
func testInvalidURL() throws { | ||
assertMacroExpansion( | ||
#""" | ||
#URLByExpandingURITemplate("{nope}", ["nope": ""]) | ||
"""#, | ||
expandedSource: | ||
#""" | ||
#URLByExpandingURITemplate("{nope}", ["nope": ""]) | ||
"""#, | ||
diagnostics: [ | ||
DiagnosticSpec(message: "Processed template does not form a valid URL\n", line: 1, column: 1), | ||
], | ||
macros: testMacros) | ||
} | ||
|
||
func testMisusedTemplate() throws { | ||
assertMacroExpansion( | ||
#""" | ||
let s: StaticString = "https://api.github.com/repos/{owner}" | ||
#URLByExpandingURITemplate(s, [ | ||
"owner": "SwiftScream", | ||
]) | ||
"""#, | ||
expandedSource: | ||
#""" | ||
let s: StaticString = "https://api.github.com/repos/{owner}" | ||
#URLByExpandingURITemplate(s, [ | ||
"owner": "SwiftScream", | ||
]) | ||
"""#, | ||
diagnostics: [ | ||
DiagnosticSpec(message: "#URLByExpandingURITemplate requires a static string literal for the first argument", line: 2, column: 1), | ||
], | ||
macros: testMacros) | ||
} | ||
|
||
func testMisusedParams() throws { | ||
assertMacroExpansion( | ||
#""" | ||
let params: KeyValue<StaticString, StaticString> = ["owner": "SwiftScream"] | ||
#URLByExpandingURITemplate("https://api.github.com/repos/{owner}/{repo}/collaborators/{username}", params) | ||
"""#, | ||
expandedSource: | ||
#""" | ||
let params: KeyValue<StaticString, StaticString> = ["owner": "SwiftScream"] | ||
#URLByExpandingURITemplate("https://api.github.com/repos/{owner}/{repo}/collaborators/{username}", params) | ||
"""#, | ||
diagnostics: [ | ||
DiagnosticSpec(message: "#URLByExpandingURITemplate requires a Dictionary Literal of string literals for the second argument", line: 2, column: 1), | ||
], | ||
macros: testMacros) | ||
} | ||
|
||
|
||
func testMisusedParamKey() throws { | ||
assertMacroExpansion( | ||
#""" | ||
#URLByExpandingURITemplate("https://api.github.com/repos/{owner}/{repo}/collaborators/{username}", [ | ||
"owner": "SwiftScream", | ||
123: "URITemplate", | ||
"username": "alexdeem", | ||
]) | ||
"""#, | ||
expandedSource: | ||
#""" | ||
#URLByExpandingURITemplate("https://api.github.com/repos/{owner}/{repo}/collaborators/{username}", [ | ||
"owner": "SwiftScream", | ||
123: "URITemplate", | ||
"username": "alexdeem", | ||
]) | ||
"""#, | ||
diagnostics: [ | ||
DiagnosticSpec(message: "#URLByExpandingURITemplate requires a Dictionary Literal of string literals for the second argument", line: 1, column: 1), | ||
], | ||
macros: testMacros) | ||
} | ||
|
||
func testMisusedParamValue() throws { | ||
assertMacroExpansion( | ||
#""" | ||
#URLByExpandingURITemplate("https://api.github.com/repos/{owner}/{repo}/collaborators/{username}", [ | ||
"owner": "SwiftScream", | ||
"repo": 12345, | ||
"username": "alexdeem", | ||
]) | ||
"""#, | ||
expandedSource: | ||
#""" | ||
#URLByExpandingURITemplate("https://api.github.com/repos/{owner}/{repo}/collaborators/{username}", [ | ||
"owner": "SwiftScream", | ||
"repo": 12345, | ||
"username": "alexdeem", | ||
]) | ||
"""#, | ||
diagnostics: [ | ||
DiagnosticSpec(message: "#URLByExpandingURITemplate requires a Dictionary Literal of string literals for the second argument", line: 1, column: 1), | ||
], | ||
macros: testMacros) | ||
} | ||
} | ||
#endif |