Skip to content

Commit 5331f14

Browse files
fixes; only 2 things left to fix...
- escapeHTML macro - interpolation html escaping
1 parent 99399d7 commit 5331f14

File tree

6 files changed

+99
-84
lines changed

6 files changed

+99
-84
lines changed

Sources/HTMLKitMacros/HTMLElement.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ private extension HTMLElementMacro {
6464
/*if elementType == .escapeHTML {
6565
let array:[CustomStringConvertible] = children.compactMap({
6666
guard let child:LabeledExprSyntax = $0.labeled else { return nil }
67-
return HTMLKitUtilities.parse_inner_html(context: context, child: child, lookupFiles: [])
67+
return HTMLKitUtilities.parseInnerHTML(context: context, child: child, lookupFiles: [])
6868
})
6969
return array.map({ String(describing: $0) }).joined()
7070
}*/
71-
let data:HTMLKitUtilities.ElementData = HTMLKitUtilities.parse_arguments(context: context, children: children)
71+
let data:HTMLKitUtilities.ElementData = HTMLKitUtilities.parseArguments(context: context, children: children)
7272
return (data.innerHTML.map({ String(describing: $0) }).joined(), data.encoding)
7373
}
7474
}

Sources/HTMLKitUtilities/HTMLElementAttribute.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ public enum HTMLElementAttribute : Hashable {
4646
case virtualkeyboardpolicy(Extra.virtualkeyboardpolicy? = nil)
4747
case writingsuggestions(Extra.writingsuggestions? = nil)
4848

49-
/// This attribute adds a space and slash (" /") character before closing a void element tag.
50-
///
51-
/// Usually only used if certain browsers need it for compatibility.
49+
/// This attribute adds a space and forward slash character (" /") before closing a void element tag, and does nothing to a non-void element.
50+
///
51+
/// Usually only used to support foreign content.
5252
case trailingSlash
5353

5454
case htmx(_ attribute: HTMLElementAttribute.HTMX? = nil)

Sources/HTMLKitUtilities/HTMLKitUtilities.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import SwiftSyntaxMacros
1111
// MARK: HTMLKitUtilities
1212
public enum HTMLKitUtilities {
1313
public struct ElementData {
14+
public let trailingSlash:Bool
1415
public let encoding:HTMLEncoding
1516
public let globalAttributes:[HTMLElementAttribute]
1617
public let attributes:[String:Any]
1718
public let innerHTML:[CustomStringConvertible]
18-
public let trailingSlash:Bool
1919

2020
init(
2121
_ encoding: HTMLEncoding,

Sources/HTMLKitUtilities/LiteralElements.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,23 +134,26 @@ macro HTMLElements(
134134

135135
// MARK: HTML
136136
public protocol HTMLElement : CustomStringConvertible {
137-
var tag: String { get }
138137
var isVoid : Bool { get }
138+
var trailingSlash : Bool { get }
139+
var tag: String { get }
139140
var attributes : [HTMLElementAttribute] { get }
140141
var innerHTML : [CustomStringConvertible] { get }
141142
}
142143

143144
/// A custom HTML element.
144145
public struct custom : HTMLElement {
145-
public var tag:String
146146
public var isVoid:Bool
147+
public var trailingSlash:Bool
148+
public var tag:String
147149
public var attributes:[HTMLElementAttribute]
148150
public var innerHTML:[CustomStringConvertible]
149151

150152
public init(context: some MacroExpansionContext, _ children: SyntaxChildren) {
151-
let data:HTMLKitUtilities.ElementData = HTMLKitUtilities.parse_arguments(context: context, children: children)
153+
let data:HTMLKitUtilities.ElementData = HTMLKitUtilities.parseArguments(context: context, children: children)
152154
tag = data.attributes["tag"] as? String ?? ""
153155
isVoid = data.attributes["isVoid"] as? Bool ?? false
156+
trailingSlash = data.trailingSlash
154157
attributes = data.globalAttributes
155158
innerHTML = data.innerHTML
156159
}
@@ -162,12 +165,13 @@ public struct custom : HTMLElement {
162165
) {
163166
self.tag = tag
164167
self.isVoid = isVoid
168+
trailingSlash = attributes.contains(.trailingSlash)
165169
self.attributes = attributes
166170
self.innerHTML = innerHTML
167171
}
168172

169173
public var description : String {
170-
return "<" + tag + ">" + (isVoid ? "" : "</" + tag + ">")
174+
return "<" + tag + (isVoid && trailingSlash ? " /" : "") + ">" + (isVoid ? "" : "</" + tag + ">")
171175
}
172176
}
173177

Sources/HTMLKitUtilities/ParseData.swift

Lines changed: 75 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import SwiftSyntaxMacros
1111

1212
public extension HTMLKitUtilities {
1313
// MARK: Parse Arguments
14-
static func parse_arguments(
14+
static func parseArguments(
1515
context: some MacroExpansionContext,
1616
children: SyntaxChildren,
1717
otherAttributes: [String:String] = [:]
@@ -24,7 +24,7 @@ public extension HTMLKitUtilities {
2424
var lookupFiles:Set<String> = []
2525
for element in children {
2626
if let child:LabeledExprSyntax = element.labeled {
27-
if var key:String = child.label?.text {
27+
if let key:String = child.label?.text {
2828
if key == "encoding" {
2929
if let key:String = child.expression.memberAccess?.declName.baseName.text {
3030
encoding = HTMLEncoding(rawValue: key) ?? .string
@@ -34,12 +34,10 @@ public extension HTMLKitUtilities {
3434
} else if key == "lookupFiles" {
3535
lookupFiles = Set(child.expression.array!.elements.compactMap({ $0.expression.stringLiteral?.string }))
3636
} else if key == "attributes" {
37-
(global_attributes, trailingSlash) = parse_global_attributes(context: context, array: child.expression.array!.elements, lookupFiles: lookupFiles)
37+
(global_attributes, trailingSlash) = parseGlobalAttributes(context: context, array: child.expression.array!.elements, lookupFiles: lookupFiles)
3838
} else {
3939
var target_key:String = key
40-
if key == "acceptCharset" {
41-
key = "accept-charset"
42-
} else if let target:String = otherAttributes[key] {
40+
if let target:String = otherAttributes[key] {
4341
target_key = target
4442
}
4543
if let test:any HTMLInitializable = HTMLElementAttribute.Extra.parse(context: context, key: target_key, expr: child.expression) {
@@ -55,15 +53,15 @@ public extension HTMLKitUtilities {
5553
}
5654
}
5755
// inner html
58-
} else if let inner_html:CustomStringConvertible = parse_inner_html(context: context, child: child, lookupFiles: lookupFiles) {
56+
} else if let inner_html:CustomStringConvertible = parseInnerHTML(context: context, child: child, lookupFiles: lookupFiles) {
5957
innerHTML.append(inner_html)
6058
}
6159
}
6260
}
6361
return ElementData(encoding, global_attributes, attributes, innerHTML, trailingSlash)
6462
}
6563
// MARK: Parse Global Attributes
66-
static func parse_global_attributes(
64+
static func parseGlobalAttributes(
6765
context: some MacroExpansionContext,
6866
array: ArrayElementListSyntax,
6967
lookupFiles: Set<String>
@@ -95,11 +93,9 @@ public extension HTMLKitUtilities {
9593
}
9694
return (attributes, trailingSlash)
9795
}
98-
static func global_attribute_already_defined(context: some MacroExpansionContext, attribute: String, node: some SyntaxProtocol) {
99-
context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "globalAttributeAlreadyDefined", message: "Global attribute \"" + attribute + "\" is already defined.")))
100-
}
96+
10197
// MARK: Parse innerHTML
102-
static func parse_inner_html(
98+
static func parseInnerHTML(
10399
context: some MacroExpansionContext,
104100
child: LabeledExprSyntax,
105101
lookupFiles: Set<String>
@@ -115,16 +111,6 @@ public extension HTMLKitUtilities {
115111
return nil
116112
}
117113
}
118-
static func unallowed_expression(context: some MacroExpansionContext, node: LabeledExprSyntax) {
119-
context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "unallowedExpression", message: "String Interpolation is required when encoding runtime values."), fixIts: [
120-
FixIt(message: DiagnosticMsg(id: "useStringInterpolation", message: "Use String Interpolation."), changes: [
121-
FixIt.Change.replace(
122-
oldNode: Syntax(node),
123-
newNode: Syntax(StringLiteralExprSyntax(content: "\\(\(node))"))
124-
)
125-
])
126-
]))
127-
}
128114

129115
// MARK: Parse element
130116
static func parse_element(context: some MacroExpansionContext, expr: ExprSyntax) -> HTMLElement? {
@@ -163,7 +149,7 @@ public extension HTMLKitUtilities {
163149
interpolation = stringLiteral.segments.compactMap({ $0.as(ExpressionSegmentSyntax.self) })
164150
}
165151
for expr in interpolation {
166-
string.replace("\(expr)", with: promote_interpolation(context: context, remaining_interpolation: &remaining_interpolation, expr: expr, lookupFiles: lookupFiles))
152+
string.replace("\(expr)", with: promoteInterpolation(context: context, remaining_interpolation: &remaining_interpolation, expr: expr, lookupFiles: lookupFiles))
167153
}
168154
if remaining_interpolation > 0 {
169155
warn_interpolation(context: context, node: expression, string: &string, remaining_interpolation: &remaining_interpolation, lookupFiles: lookupFiles)
@@ -178,8 +164,55 @@ public extension HTMLKitUtilities {
178164
}
179165
return returnType
180166
}
167+
// MARK: Promote Interpolation
168+
static func promoteInterpolation(
169+
context: some MacroExpansionContext,
170+
remaining_interpolation: inout Int,
171+
expr: ExpressionSegmentSyntax,
172+
lookupFiles: Set<String>
173+
) -> String {
174+
var string:String = "\(expr)"
175+
guard let expression:ExprSyntax = expr.expressions.first?.expression else { return string }
176+
if let stringLiteral:StringLiteralExprSyntax = expression.stringLiteral {
177+
let segments:StringLiteralSegmentListSyntax = stringLiteral.segments
178+
if segments.count(where: { $0.is(StringSegmentSyntax.self) }) == segments.count {
179+
remaining_interpolation = 0
180+
string = segments.map({ $0.as(StringSegmentSyntax.self)!.content.text }).joined()
181+
} else {
182+
string = ""
183+
for segment in segments {
184+
if let literal:String = segment.as(StringSegmentSyntax.self)?.content.text {
185+
string += literal
186+
} else if let interpolation:ExpressionSegmentSyntax = segment.as(ExpressionSegmentSyntax.self) {
187+
let promoted:String = promoteInterpolation(context: context, remaining_interpolation: &remaining_interpolation, expr: interpolation, lookupFiles: lookupFiles)
188+
if "\(interpolation)" == promoted {
189+
//string += "\\(\"\(promoted)\".escapingHTML(escapeAttributes: true))"
190+
string += "\(promoted)"
191+
warn_interpolation(context: context, node: interpolation, string: &string, remaining_interpolation: &remaining_interpolation, lookupFiles: lookupFiles)
192+
} else {
193+
string += promoted
194+
}
195+
} else {
196+
//string += "\\(\"\(segment)\".escapingHTML(escapeAttributes: true))"
197+
warn_interpolation(context: context, node: segment, string: &string, remaining_interpolation: &remaining_interpolation, lookupFiles: lookupFiles)
198+
string += "\(segment)"
199+
}
200+
}
201+
}
202+
} else if let fix:String = expression.integerLiteral?.literal.text ?? expression.floatLiteral?.literal.text {
203+
let target:String = "\(expr)"
204+
remaining_interpolation -= string.ranges(of: target).count
205+
string.replace(target, with: fix)
206+
} else {
207+
//string = "\\(\"\(string)\".escapingHTML(escapeAttributes: true))"
208+
warn_interpolation(context: context, node: expr, string: &string, remaining_interpolation: &remaining_interpolation, lookupFiles: lookupFiles)
209+
}
210+
return string
211+
}
212+
}
213+
extension HTMLKitUtilities {
181214
// MARK: Extract literal
182-
private static func extract_literal(
215+
static func extract_literal(
183216
context: some MacroExpansionContext,
184217
key: String,
185218
expression: ExprSyntax,
@@ -253,52 +286,25 @@ public extension HTMLKitUtilities {
253286
}
254287
return nil
255288
}
256-
// MARK: Promote Interpolation
257-
static func promote_interpolation(
258-
context: some MacroExpansionContext,
259-
remaining_interpolation: inout Int,
260-
expr: ExpressionSegmentSyntax,
261-
lookupFiles: Set<String>
262-
) -> String {
263-
var string:String = "\(expr)"
264-
guard let expression:ExprSyntax = expr.expressions.first?.expression else { return string }
265-
if let stringLiteral:StringLiteralExprSyntax = expression.stringLiteral {
266-
let segments:StringLiteralSegmentListSyntax = stringLiteral.segments
267-
if segments.count(where: { $0.is(StringSegmentSyntax.self) }) == segments.count {
268-
remaining_interpolation = 0
269-
string = segments.map({ $0.as(StringSegmentSyntax.self)!.content.text }).joined()
270-
} else {
271-
string = ""
272-
for segment in segments {
273-
if let literal:String = segment.as(StringSegmentSyntax.self)?.content.text {
274-
string += literal
275-
} else if let interpolation:ExpressionSegmentSyntax = segment.as(ExpressionSegmentSyntax.self) {
276-
let promoted:String = promote_interpolation(context: context, remaining_interpolation: &remaining_interpolation, expr: interpolation, lookupFiles: lookupFiles)
277-
if "\(interpolation)" == promoted {
278-
//string += "\\(\"\(promoted)\".escapingHTML(escapeAttributes: true))"
279-
string += "\(promoted)"
280-
warn_interpolation(context: context, node: interpolation, string: &string, remaining_interpolation: &remaining_interpolation, lookupFiles: lookupFiles)
281-
} else {
282-
string += promoted
283-
}
284-
} else {
285-
//string += "\\(\"\(segment)\".escapingHTML(escapeAttributes: true))"
286-
warn_interpolation(context: context, node: segment, string: &string, remaining_interpolation: &remaining_interpolation, lookupFiles: lookupFiles)
287-
string += "\(segment)"
288-
}
289-
}
290-
}
291-
} else if let fix:String = expression.integerLiteral?.literal.text ?? expression.floatLiteral?.literal.text {
292-
let target:String = "\(expr)"
293-
remaining_interpolation -= string.ranges(of: target).count
294-
string.replace(target, with: fix)
295-
} else {
296-
//string = "\\(\"\(string)\".escapingHTML(escapeAttributes: true))"
297-
warn_interpolation(context: context, node: expr, string: &string, remaining_interpolation: &remaining_interpolation, lookupFiles: lookupFiles)
298-
}
299-
return string
289+
290+
// MARK: GA Already Defined
291+
static func global_attribute_already_defined(context: some MacroExpansionContext, attribute: String, node: some SyntaxProtocol) {
292+
context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "globalAttributeAlreadyDefined", message: "Global attribute \"" + attribute + "\" is already defined.")))
293+
}
294+
295+
// MARK: Unallowed Expression
296+
static func unallowed_expression(context: some MacroExpansionContext, node: LabeledExprSyntax) {
297+
context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "unallowedExpression", message: "String Interpolation is required when encoding runtime values."), fixIts: [
298+
FixIt(message: DiagnosticMsg(id: "useStringInterpolation", message: "Use String Interpolation."), changes: [
299+
FixIt.Change.replace(
300+
oldNode: Syntax(node),
301+
newNode: Syntax(StringLiteralExprSyntax(content: "\\(\(node))"))
302+
)
303+
])
304+
]))
300305
}
301-
// MARK: warn interpolation
306+
307+
// MARK: Warn Interpolation
302308
static func warn_interpolation(
303309
context: some MacroExpansionContext,
304310
node: some SyntaxProtocol,

Sources/HTMLKitUtilityMacros/HTMLElements.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ enum HTMLElements : DeclarationMacro {
3434
element = "`var`"
3535
}
3636
var string:String = "/// MARK: \(element)\n/// The `\(element)` HTML element.\npublic struct \(element) : HTMLElement {\n"
37-
string += "public private(set) var tag:String = \"\(element)\"\n"
3837
string += "public private(set) var isVoid:Bool = \(is_void)\n"
38+
string += "public var trailingSlash:Bool = false\n"
39+
string += "public private(set) var tag:String = \"\(element)\"\n"
3940
string += "public var attributes:[HTMLElementAttribute]\n"
4041

4142
var initializers:String = ""
@@ -99,7 +100,10 @@ enum HTMLElements : DeclarationMacro {
99100

100101
initializers += "public init?(context: some MacroExpansionContext, _ children: SyntaxChildren) {\n"
101102
let other_attributes_string:String = other_attributes.isEmpty ? "" : ", otherAttributes: [" + other_attributes.map({ "\"" + $0.0 + "\":\"" + $0.1 + "\"" }).joined(separator: ",") + "]"
102-
initializers += "let data:HTMLKitUtilities.ElementData = HTMLKitUtilities.parse_arguments(context: context, children: children\(other_attributes_string))\n"
103+
initializers += "let data:HTMLKitUtilities.ElementData = HTMLKitUtilities.parseArguments(context: context, children: children\(other_attributes_string))\n"
104+
if is_void {
105+
initializers += "self.trailingSlash = data.trailingSlash\n"
106+
}
103107
initializers += "self.attributes = data.globalAttributes\n"
104108
for (key, value_type, _) in attributes {
105109
var key_literal:String = key
@@ -166,15 +170,16 @@ enum HTMLElements : DeclarationMacro {
166170
} else {
167171
attributes_func += "\n"
168172
attributes_func += "if let \(key), let v:String = \(key).htmlValue {\n"
169-
attributes_func += "let s:String = \(key).htmlValueIsVoidable && v.isEmpty ? \"\" : \"=\\\"\" + v + \"\\\"\"\n"
170-
attributes_func += #"items.append("\#(key_literal)" + s)"#
173+
attributes_func += #"let s:String = \#(key).htmlValueIsVoidable && v.isEmpty ? "" : "=\\\"" + v + "\\\"""#
174+
attributes_func += "\nitems.append(\"\(key_literal)\" + s)"
171175
attributes_func += "\n}"
172176
}
173177
}
174178
attributes_func += "\nreturn (items.isEmpty ? \"\" : \" \") + items.joined(separator: \" \")\n}\n"
175179
render += attributes_func
176180
render += "let string:String = innerHTML.map({ String(describing: $0) }).joined()\n"
177-
render += "return \"\(element == "html" ? "<!DOCTYPE html>" : "")<\(element)\" + attributes() + \">\" + string" + (is_void ? "" : " + \"</\(element)>\"")
181+
let trailing_slash:String = is_void ? " + (trailingSlash ? \" /\" : \"\")" : ""
182+
render += "return \"\(element == "html" ? "<!DOCTYPE html>" : "")<\(element)\" + attributes()\(trailing_slash) + \">\" + string" + (is_void ? "" : " + \"</\(element)>\"")
178183
render += "}"
179184

180185
string += render

0 commit comments

Comments
 (0)