Skip to content

Commit 8d5c3b4

Browse files
fixed allowing more than 1 of the same global attribute in the compiled result
- worth noting that I'd rather implement it this way than to use another dependency
1 parent afbac3d commit 8d5c3b4

File tree

2 files changed

+39
-23
lines changed

2 files changed

+39
-23
lines changed

Sources/HTMLKitMacros/HTMLElement.swift

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import HTMLKitUtilities
1313
struct HTMLElement : ExpressionMacro {
1414
static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax {
1515
let type:HTMLElementType = HTMLElementType(rawValue: node.macroName.text)!
16-
let data:ElementData = parse_arguments(elementType: type, arguments: node.arguments)
16+
let data:ElementData = parse_arguments(context: context, elementType: type, arguments: node.arguments)
1717
var string:String = (type == .html ? "<!DOCTYPE html>" : "") + "<" + type.rawValue + data.attributes + ">" + data.innerHTML
1818
if !type.isVoid {
1919
string += "</" + type.rawValue + ">"
@@ -23,25 +23,25 @@ struct HTMLElement : ExpressionMacro {
2323
}
2424

2525
private extension HTMLElement {
26-
static func parse_arguments(elementType: HTMLElementType, arguments: LabeledExprListSyntax) -> ElementData {
26+
static func parse_arguments(context: some MacroExpansionContext, elementType: HTMLElementType, arguments: LabeledExprListSyntax) -> ElementData {
2727
var attributes:[String] = [], innerHTML:[String] = []
2828
for element in arguments.children(viewMode: .all) {
2929
if let child:LabeledExprSyntax = element.as(LabeledExprSyntax.self) {
3030
if var key:String = child.label?.text { // attributes
3131
if key == "attributes" {
32-
attributes.append(contentsOf: parse_global_attributes(elementType: elementType, array: child.expression.as(ArrayExprSyntax.self)!))
32+
attributes.append(contentsOf: parse_global_attributes(context: context, elementType: elementType, array: child.expression.as(ArrayExprSyntax.self)!))
3333
} else {
3434
if key == "acceptCharset" {
3535
key = "accept-charset"
3636
}
3737
if let string:String = parse_attribute(elementType: elementType, key: key, expression: child.expression) {
38-
attributes.append(string)
38+
attributes.append(key + (string.isEmpty ? "" : "=\\\"" + string + "\\\""))
3939
}
4040
}
4141
} else if let array:ArrayElementListSyntax = child.expression.as(ArrayExprSyntax.self)?.elements { // inner html
4242
for yoink in array {
4343
if let macro:MacroExpansionExprSyntax = yoink.expression.as(MacroExpansionExprSyntax.self) {
44-
innerHTML.append(parse_element_macro(expression: macro))
44+
innerHTML.append(parse_element_macro(context: context, expression: macro))
4545
} else if let string:String = yoink.expression.as(StringLiteralExprSyntax.self)?.string {
4646
innerHTML.append(string)
4747
}
@@ -51,30 +51,38 @@ private extension HTMLElement {
5151
}
5252
return ElementData(attributes: attributes, innerHTML: innerHTML)
5353
}
54-
static func parse_global_attributes(elementType: HTMLElementType, array: ArrayExprSyntax) -> [String] {
55-
var attributes:[String] = []
54+
static func parse_global_attributes(context: some MacroExpansionContext, elementType: HTMLElementType, array: ArrayExprSyntax) -> [String] {
55+
var keys:Set<String> = [], attributes:[String] = []
5656
for element in array.elements {
5757
let function:FunctionCallExprSyntax = element.expression.as(FunctionCallExprSyntax.self)!
58-
var key:String = function.calledExpression.as(MemberAccessExprSyntax.self)!.declName.baseName.text
58+
var key:String = function.calledExpression.as(MemberAccessExprSyntax.self)!.declName.baseName.text, value:String? = nil
5959
if key == "data" {
60-
var (value, returnType):(String, LiteralReturnType) = parse_literal_value(elementType: elementType, key: "data", expression: function.arguments.last!.expression)!
60+
var (literalValue, returnType):(String, LiteralReturnType) = parse_literal_value(elementType: elementType, key: "data", expression: function.arguments.last!.expression)!
6161
if returnType == .interpolation {
62-
value = "\\(" + value + ")"
62+
literalValue = "\\(" + literalValue + ")"
6363
}
64+
value = literalValue
6465
key += "-\(function.arguments.first!.expression.as(StringLiteralExprSyntax.self)!.string)"
65-
attributes.append(key + "=\\\"" + value + "\\\"")
6666
} else if key == "event" {
67-
key = function.arguments.first!.expression.as(MemberAccessExprSyntax.self)!.declName.baseName.text
68-
attributes.append("on" + key + "=\\\"" + function.arguments.last!.expression.as(StringLiteralExprSyntax.self)!.string + "\\\"")
67+
key = "on" + function.arguments.first!.expression.as(MemberAccessExprSyntax.self)!.declName.baseName.text
68+
value = function.arguments.last!.expression.as(StringLiteralExprSyntax.self)!.string
6969
} else if let string:String = parse_attribute(elementType: elementType, key: key, expression: function.arguments.first!.expression) {
70-
attributes.append(string)
70+
value = string
71+
}
72+
if let value:String = value {
73+
if keys.contains(key) {
74+
context.diagnose(Diagnostic(node: element, message: ErrorDiagnostic(id: "globalAttributeAlreadyDefined", message: "Global attribute is already defined.")))
75+
} else {
76+
attributes.append(key + (value.isEmpty ? "" : "=\\\"" + value + "\\\""))
77+
keys.insert(key)
78+
}
7179
}
7280
}
7381
return attributes
7482
}
75-
static func parse_element_macro(expression: MacroExpansionExprSyntax) -> String {
83+
static func parse_element_macro(context: some MacroExpansionContext, expression: MacroExpansionExprSyntax) -> String {
7684
guard let elementType:HTMLElementType = HTMLElementType(rawValue: expression.macroName.text) else { return "\(expression)" }
77-
let data:ElementData = parse_arguments(elementType: elementType, arguments: expression.arguments)
85+
let data:ElementData = parse_arguments(context: context, elementType: elementType, arguments: expression.arguments)
7886
return "<" + elementType.rawValue + data.attributes + ">" + data.innerHTML + (elementType.isVoid ? "" : "</" + elementType.rawValue + ">")
7987
}
8088

@@ -98,12 +106,11 @@ private extension HTMLElement {
98106
}
99107

100108
static func parse_attribute(elementType: HTMLElementType, key: String, expression: ExprSyntax) -> String? {
101-
func yup(_ value: String) -> String { key + "=\\\"" + value + "\\\"" }
102109
if let (string, returnType):(String, LiteralReturnType) = parse_literal_value(elementType: elementType, key: key, expression: expression) {
103110
switch returnType {
104-
case .boolean: return string.elementsEqual("true") ? key : nil
105-
case .string: return yup(string)
106-
case .interpolation: return yup("\\(" + string + ")")
111+
case .boolean: return string.elementsEqual("true") ? "" : nil
112+
case .string: return string
113+
case .interpolation: return "\\(" + string + ")"
107114
}
108115
}
109116
if let value:String = expression.as(ArrayExprSyntax.self)?.elements.compactMap({
@@ -118,12 +125,12 @@ private extension HTMLElement {
118125
}
119126
return nil
120127
}).joined(separator: get_separator(key: key)) {
121-
return yup(value)
128+
return value
122129
}
123130
func member(_ value: String) -> String {
124131
var string:String = String(value[value.index(after: value.startIndex)...])
125132
string = HTMLElementAttribute.Extra.htmlValue(enumName: enumName(elementType: elementType, key: key), for: string)
126-
return yup(string)
133+
return string
127134
}
128135
if let function:FunctionCallExprSyntax = expression.as(FunctionCallExprSyntax.self) {
129136
return member("\(function)")

Tests/HTMLKitTests/HTMLKitTests.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ extension HTMLKitTests {
8989
])
9090
#expect(string == "<div><div></div><div><div></div><div></div><div></div></div><div></div></div>")
9191
}
92+
/*@Test func test_same_attribute_multiple_times() {
93+
let string:StaticString = #div(attributes: [.id("1"), .id("2"), .id("3"), .id("4")])
94+
#expect(string == "<div id=\"1\"></div>")
95+
}*/
96+
@Test func test_attribute_hidden() {
97+
#expect(#div(attributes: [.hidden(.true)]) == "<div hidden></div>")
98+
#expect(#div(attributes: [.hidden(.untilFound)]) == "<div hidden=\"until-found\"></div>")
99+
}
100+
92101
@Test func test_void() {
93102
let string:StaticString = #area([#base(), #br(), #col(), #embed(), #hr(), #img(), #input(), #link(), #meta(), #source(), #track(), #wbr()])
94103
#expect(string == "<area><base><br><col><embed><hr><img><input><link><meta><source><track><wbr>")
@@ -165,7 +174,7 @@ extension HTMLKitTests {
165174
)
166175
])
167176
])
168-
#expect(test == "<!DOCTYPE html><html><body><div class=\"dark-mode row\" draggable=\"false\" hidden=\"\" inputmode=\"email\" title=\"Hey, you're pretty cool\">Random text<div></div><a><div><abbr></abbr></div><address></address></a><div></div><button disabled></button><video autoplay preload=\"auto\" src=\"https://github.com/RandomHashTags/litleagues\" width=\"1cm\"></video></div></body></html>")
177+
#expect(test == "<!DOCTYPE html><html><body><div class=\"dark-mode row\" draggable=\"false\" hidden inputmode=\"email\" title=\"Hey, you're pretty cool\">Random text<div></div><a><div><abbr></abbr></div><address></address></a><div></div><button disabled></button><video autoplay preload=\"auto\" src=\"https://github.com/RandomHashTags/litleagues\" width=\"1cm\"></video></div></body></html>")
169178
}
170179
}
171180

0 commit comments

Comments
 (0)