Skip to content

Commit d563562

Browse files
attribute values now accept syntax for third-party function calls, literals and enums
1 parent 174b487 commit d563562

File tree

4 files changed

+123
-29
lines changed

4 files changed

+123
-29
lines changed

Package.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ let package = Package(
2929
"HTMLKitUtilities",
3030
.product(name: "SwiftSyntax", package: "swift-syntax"),
3131
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
32-
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
32+
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
33+
.product(name: "SwiftDiagnostics", package: "swift-syntax")
3334
]
3435
),
3536
.target(

Sources/HTMLKitMacros/HTMLElement.swift

+80-28
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import SwiftSyntax
99
import SwiftSyntaxMacros
10+
import SwiftDiagnostics
1011
import HTMLKitUtilities
1112

1213
struct HTMLElement : ExpressionMacro {
@@ -28,9 +29,14 @@ private extension HTMLElement {
2829
if let child:LabeledExprSyntax = element.as(LabeledExprSyntax.self) {
2930
if var key:String = child.label?.text { // attributes
3031
if key == "data" {
31-
let tuple:TupleExprSyntax = child.expression.as(TupleExprSyntax.self)!
32+
//context.diagnose(Diagnostic(node: node, message: ErrorDiagnostic(id: "bro", message: child.expression.debugDescription)))
33+
let tuple:TupleExprSyntax = child.expression.as(TupleExprSyntax.self)!, valueExpression:ExprSyntax = tuple.elements.last!.expression
34+
var (value, returnType):(String, LiteralReturnType) = parse_literal_value(elementType: elementType, key: "data", expression: valueExpression)!
35+
if returnType == .interpolation {
36+
value = "\\(" + value + ")"
37+
}
3238
key += "-\(tuple.elements.first!.expression.as(StringLiteralExprSyntax.self)!.string)"
33-
attributes.append(key + "=\\\"\(tuple.elements.last!.expression.as(StringLiteralExprSyntax.self)!.string)\\\"")
39+
attributes.append(key + "=\\\"" + value + "\\\"")
3440
} else {
3541
if key == "acceptCharset" {
3642
key = "accept-charset"
@@ -67,54 +73,47 @@ private extension HTMLElement {
6773
}
6874
}
6975

70-
static func parse_attribute(elementType: HTMLElementType, key: String, expression: ExprSyntax) -> String? {
71-
if let boolean:String = expression.as(BooleanLiteralExprSyntax.self)?.literal.text {
72-
return boolean.elementsEqual("true") ? key : nil
76+
static func enumName(elementType: HTMLElementType, key: String) -> String {
77+
switch elementType.rawValue + key {
78+
case "buttontype": return "buttontype"
79+
case "inputtype": return "inputmode"
80+
case "oltype": return "numberingtype"
81+
case "scripttype": return "scripttype"
82+
default: return key
7383
}
84+
}
85+
86+
static func parse_attribute(elementType: HTMLElementType, key: String, expression: ExprSyntax) -> String? {
7487
func yup(_ value: String) -> String { key + "=\\\"" + value + "\\\"" }
75-
if let string:String = expression.as(StringLiteralExprSyntax.self)?.string {
76-
return yup(string)
77-
}
78-
if let integer:String = expression.as(IntegerLiteralExprSyntax.self)?.literal.text {
79-
return yup(integer)
80-
}
81-
if let float:String = expression.as(FloatLiteralExprSyntax.self)?.literal.text {
82-
return yup(float)
83-
}
84-
func enumName() -> String {
85-
switch elementType.rawValue + key { // better performance than switching key, than switching elementType
86-
case "buttontype": return "buttontype"
87-
case "inputtype": return "inputmode"
88-
case "oltype": return "numberingtype"
89-
case "scripttype": return "scripttype"
90-
default: return key
88+
if let (string, returnType):(String, LiteralReturnType) = parse_literal_value(elementType: elementType, key: key, expression: expression) {
89+
switch returnType {
90+
case .boolean: return string.elementsEqual("true") ? key : nil
91+
case .string: return yup(string)
92+
case .interpolation: return yup("\\(" + string + ")")
9193
}
9294
}
9395
if let value:String = expression.as(ArrayExprSyntax.self)?.elements.compactMap({
94-
if let string:String = $0.expression.as(StringLiteralExprSyntax.self)?.string {
96+
if let string:String = $0.expression.stringLiteral?.string {
9597
return string
9698
}
97-
if let string:String = $0.expression.as(IntegerLiteralExprSyntax.self)?.literal.text {
99+
if let string:String = $0.expression.integerLiteral?.literal.text {
98100
return string
99101
}
100102
if let string:String = $0.expression.as(MemberAccessExprSyntax.self)?.declName.baseName.text {
101-
return HTMLElementAttribute.htmlValue(enumName: enumName(), for: string)
103+
return HTMLElementAttribute.htmlValue(enumName: enumName(elementType: elementType, key: key), for: string)
102104
}
103105
return nil
104106
}).joined(separator: get_separator(key: key)) {
105107
return yup(value)
106108
}
107109
func member(_ value: String) -> String {
108110
var string:String = String(value[value.index(after: value.startIndex)...])
109-
string = HTMLElementAttribute.htmlValue(enumName: enumName(), for: string)
111+
string = HTMLElementAttribute.htmlValue(enumName: enumName(elementType: elementType, key: key), for: string)
110112
return yup(string)
111113
}
112114
if let function:FunctionCallExprSyntax = expression.as(FunctionCallExprSyntax.self) {
113115
return member("\(function)")
114116
}
115-
if let value:String = expression.as(MemberAccessExprSyntax.self)?.declName.baseName.text {
116-
return member("." + value)
117-
}
118117
return nil
119118
}
120119
static func get_separator(key: String) -> String {
@@ -123,6 +122,53 @@ private extension HTMLElement {
123122
default: return " "
124123
}
125124
}
125+
static func parse_literal_value(elementType: HTMLElementType, key: String, expression: ExprSyntax) -> (value: String, returnType: LiteralReturnType)? {
126+
if let boolean:String = expression.booleanLiteral?.literal.text {
127+
return (boolean, .boolean)
128+
}
129+
if let string:String = expression.stringLiteral?.string {
130+
return (string, .string)
131+
}
132+
if let integer:String = expression.integerLiteral?.literal.text {
133+
return (integer, .string)
134+
}
135+
if let float:String = expression.floatLiteral?.literal.text {
136+
return (float, .string)
137+
}
138+
if let function:FunctionCallExprSyntax = expression.as(FunctionCallExprSyntax.self) {
139+
switch key {
140+
case "height", "width":
141+
var value:String = "\(function)"
142+
value = String(value[value.index(after: value.startIndex)...])
143+
value = HTMLElementAttribute.htmlValue(enumName: enumName(elementType: elementType, key: key), for: value)
144+
return (value, .string)
145+
default:
146+
return ("\(function)", .interpolation)
147+
}
148+
}
149+
if let member:MemberAccessExprSyntax = expression.as(MemberAccessExprSyntax.self) {
150+
let decl:String = member.declName.baseName.text
151+
if let base:ExprSyntax = member.base {
152+
if let integer:String = base.integerLiteral?.literal.text {
153+
switch decl {
154+
case "description":
155+
return (integer, .string)
156+
default:
157+
return (integer, .interpolation)
158+
}
159+
} else {
160+
return ("\(member)", .interpolation)
161+
}
162+
} else {
163+
return (HTMLElementAttribute.htmlValue(enumName: enumName(elementType: elementType, key: key), for: decl), .string)
164+
}
165+
}
166+
return nil
167+
}
168+
}
169+
170+
enum LiteralReturnType {
171+
case boolean, string, interpolation
126172
}
127173

128174
// MARK: HTMLElementType
@@ -270,6 +316,12 @@ enum HTMLElementType : String {
270316
}
271317
}
272318

319+
extension ExprSyntax {
320+
var booleanLiteral : BooleanLiteralExprSyntax? { self.as(BooleanLiteralExprSyntax.self) }
321+
var stringLiteral : StringLiteralExprSyntax? { self.as(StringLiteralExprSyntax.self) }
322+
var integerLiteral : IntegerLiteralExprSyntax? { self.as(IntegerLiteralExprSyntax.self) }
323+
var floatLiteral : FloatLiteralExprSyntax? { self.as(FloatLiteralExprSyntax.self) }
324+
}
273325
extension StringLiteralExprSyntax {
274326
var string : String { "\(segments)" }
275327
}

Sources/HTMLKitMacros/HTMLKitMacros.swift

+14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@
77

88
import SwiftCompilerPlugin
99
import SwiftSyntaxMacros
10+
import SwiftDiagnostics
11+
12+
// MARK: ErrorDiagnostic
13+
struct ErrorDiagnostic : DiagnosticMessage {
14+
let message:String
15+
let diagnosticID:MessageID
16+
let severity:DiagnosticSeverity = DiagnosticSeverity.error
17+
18+
init(id: String, message: String) {
19+
self.message = message
20+
self.diagnosticID = MessageID(domain: "HTMLKitMacros", id: id)
21+
}
22+
}
23+
1024

1125
@main
1226
struct HTMLKitMacros : CompilerPlugin {

Tests/HTMLKitTests/HTMLKitTests.swift

+27
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,33 @@ extension HTMLKitTests {
109109
}
110110
}
111111

112+
extension HTMLKitTests {
113+
enum Shrek : String {
114+
case isLove, isLife
115+
}
116+
@Test func test_third_party_enum() {
117+
#expect(#div(title: Shrek.isLove.rawValue) == "<div title=\"isLove\"></div>")
118+
#expect(#div(title: "\(Shrek.isLife)") == "<div title=\"isLife\"></div>")
119+
}
120+
}
121+
122+
extension HTMLKitTests {
123+
static let spongebob:String = "Spongebob"
124+
static func spongebobCharacter(_ string: String) -> String {
125+
switch string {
126+
case "patrick": return "Patrick Star"
127+
default: return "Plankton"
128+
}
129+
}
130+
131+
@Test func test_third_party_literal() {
132+
#expect(#div(title: HTMLKitTests.spongebob) == "<div title=\"Spongebob\"></div>")
133+
}
134+
@Test func test_third_party_func() {
135+
#expect(#div(title: HTMLKitTests.spongebobCharacter("patrick")) == "<div title=\"Patrick Star\"></div>")
136+
}
137+
}
138+
112139
extension HTMLKitTests {
113140
@Test func test_example_1() {
114141
let test:String = #html([

0 commit comments

Comments
 (0)