Skip to content

Commit 99a88c0

Browse files
all unit tests now pass
1 parent 5331f14 commit 99a88c0

File tree

12 files changed

+97
-82
lines changed

12 files changed

+97
-82
lines changed

Sources/HTMLKit/HTMLKit.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public extension StringProtocol {
1919
}
2020

2121
@freestanding(expression)
22-
public macro escapeHTML<T: ExpressibleByStringLiteral>(_ innerHTML: T...) -> T = #externalMacro(module: "HTMLKitMacros", type: "HTMLElementMacro")
22+
public macro escapeHTML<T: CustomStringConvertible>(_ innerHTML: T...) -> T = #externalMacro(module: "HTMLKitMacros", type: "EscapeHTML")
2323

2424
// MARK: HTML Representation
2525
@freestanding(expression)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// EscapeHTML.swift
3+
//
4+
//
5+
// Created by Evan Anderson on 11/23/24.
6+
//
7+
8+
import HTMLKitUtilities
9+
import SwiftSyntax
10+
import SwiftSyntaxMacros
11+
12+
enum EscapeHTML : ExpressionMacro {
13+
static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax {
14+
return "\"\(raw: HTMLKitUtilities.escapeHTML(expansion: node.macroExpansion!, context: context))\""
15+
}
16+
}

Sources/HTMLKitMacros/HTMLElement.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ import SwiftDiagnostics
1010
import SwiftSyntax
1111
import SwiftSyntaxMacros
1212

13-
#if canImport(Foundation)
14-
import struct Foundation.Data
15-
#endif
16-
1713
enum HTMLElementMacro : ExpressionMacro {
1814
static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax {
1915
let (string, encoding):(String, HTMLEncoding) = expand_macro(context: context, macro: node.macroExpansion!)

Sources/HTMLKitMacros/HTMLKitMacros.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import SwiftSyntaxMacros
1111
@main
1212
struct HTMLKitMacros : CompilerPlugin {
1313
let providingMacros:[any Macro.Type] = [
14-
HTMLElementMacro.self
14+
HTMLElementMacro.self,
15+
EscapeHTML.self
1516
]
1617
}

Sources/HTMLKitUtilities/HTMLElementAttribute.swift

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ public enum HTMLElementAttribute : Hashable {
1616

1717
case autocapitalize(Extra.autocapitalize? = nil)
1818
case autofocus(Bool? = false)
19-
case `class`([String] = [])
19+
case `class`([String]? = nil)
2020
case contenteditable(Extra.contenteditable? = nil)
2121
case data(_ id: String, _ value: String? = nil)
2222
case dir(Extra.dir? = nil)
2323
case draggable(Extra.draggable? = nil)
2424
case enterkeyhint(Extra.enterkeyhint? = nil)
25-
case exportparts([String] = [])
25+
case exportparts([String]? = nil)
2626
case hidden(Extra.hidden? = nil)
2727
case id(String? = nil)
2828
case inert(Bool? = false)
@@ -35,7 +35,7 @@ public enum HTMLElementAttribute : Hashable {
3535
case itemtype(String? = nil)
3636
case lang(String? = nil)
3737
case nonce(String? = nil)
38-
case part([String] = [])
38+
case part([String]? = nil)
3939
case popover(Extra.popover? = nil)
4040
case slot(String? = nil)
4141
case spellcheck(Extra.spellcheck? = nil)
@@ -65,7 +65,7 @@ public enum HTMLElementAttribute : Hashable {
6565
func boolean() -> Bool? { expression.boolean(context: context, key: key) }
6666
func enumeration<T : HTMLInitializable>() -> T? { expression.enumeration(context: context, key: key, arguments: function.arguments) }
6767
func int() -> Int? { expression.int(context: context, key: key) }
68-
func array_string() -> [String] { expression.array_string(context: context, key: key) }
68+
func array_string() -> [String]? { expression.array_string(context: context, key: key) }
6969
switch key {
7070
case "accesskey": self = .accesskey(string())
7171
case "ariaattribute": self = .ariaattribute(enumeration())
@@ -184,13 +184,13 @@ public enum HTMLElementAttribute : Hashable {
184184
case .role(let value): return value?.rawValue
185185
case .autocapitalize(let value): return value?.rawValue
186186
case .autofocus(let value): return value == true ? "" : nil
187-
case .class(let value): return value.joined(separator: " ")
187+
case .class(let value): return value?.joined(separator: " ")
188188
case .contenteditable(let value): return value?.htmlValue
189189
case .data(_, let value): return value
190190
case .dir(let value): return value?.rawValue
191191
case .draggable(let value): return value?.rawValue
192192
case .enterkeyhint(let value): return value?.rawValue
193-
case .exportparts(let value): return value.joined(separator: ",")
193+
case .exportparts(let value): return value?.joined(separator: ",")
194194
case .hidden(let value): return value?.htmlValue
195195
case .id(let value): return value
196196
case .inert(let value): return value == true ? "" : nil
@@ -203,7 +203,7 @@ public enum HTMLElementAttribute : Hashable {
203203
case .itemtype(let value): return value
204204
case .lang(let value): return value
205205
case .nonce(let value): return value
206-
case .part(let value): return value.joined(separator: " ")
206+
case .part(let value): return value?.joined(separator: " ")
207207
case .popover(let value): return value?.rawValue
208208
case .slot(let value): return value
209209
case .spellcheck(let value): return value?.rawValue
@@ -222,6 +222,7 @@ public enum HTMLElementAttribute : Hashable {
222222
}
223223
}
224224

225+
// MARK: htmlValueIsVoidable
225226
public var htmlValueIsVoidable : Bool {
226227
switch self {
227228
case .autofocus(_), .hidden(_), .inert(_), .itemscope(_):
@@ -232,4 +233,16 @@ public enum HTMLElementAttribute : Hashable {
232233
return false
233234
}
234235
}
236+
237+
// MARK: htmlValueDelimiter
238+
public var htmlValueDelimiter : String {
239+
switch self {
240+
case .htmx(let v):
241+
switch v {
242+
case .request(_, _, _, _), .headers(_, _): return "'"
243+
default: return "\\\""
244+
}
245+
default: return "\\\""
246+
}
247+
}
235248
}

Sources/HTMLKitUtilities/HTMLElementAttributeExtra.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -120,19 +120,19 @@ public extension HTMLElementAttribute.Extra {
120120
case colindex(Int?)
121121
case colindextext(String?)
122122
case colspan(Int?)
123-
case controls([String])
123+
case controls([String]?)
124124
case current(Current?)
125125

126-
case describedby([String])
126+
case describedby([String]?)
127127
case description(String?)
128-
case details([String])
128+
case details([String]?)
129129
case disabled(Bool?)
130130
case dropeffect(DropEffect?)
131131

132132
case errormessage(String?)
133133
case expanded(Expanded?)
134134

135-
case flowto([String])
135+
case flowto([String]?)
136136

137137
case grabbed(Grabbed?)
138138

@@ -144,7 +144,7 @@ public extension HTMLElementAttribute.Extra {
144144
case keyshortcuts(String?)
145145

146146
case label(String?)
147-
case labelledby([String])
147+
case labelledby([String]?)
148148
case level(Int?)
149149
case live(Live?)
150150

@@ -153,7 +153,7 @@ public extension HTMLElementAttribute.Extra {
153153
case multiselectable(Bool?)
154154

155155
case orientation(Orientation?)
156-
case owns([String])
156+
case owns([String]?)
157157

158158
case placeholder(String?)
159159
case posinset(Int?)
@@ -184,7 +184,7 @@ public extension HTMLElementAttribute.Extra {
184184
func boolean() -> Bool? { expression.boolean(context: context, key: key) }
185185
func enumeration<T : HTMLInitializable>() -> T? { expression.enumeration(context: context, key: key, arguments: arguments) }
186186
func int() -> Int? { expression.int(context: context, key: key) }
187-
func array_string() -> [String] { expression.array_string(context: context, key: key) }
187+
func array_string() -> [String]? { expression.array_string(context: context, key: key) }
188188
func float() -> Float? { expression.float(context: context, key: key) }
189189
switch key {
190190
case "activedescendant": self = .activedescendant(string())
@@ -319,30 +319,30 @@ public extension HTMLElementAttribute.Extra {
319319
case .colindex(let value): return unwrap(value)
320320
case .colindextext(let value): return value
321321
case .colspan(let value): return unwrap(value)
322-
case .controls(let value): return value.joined(separator: " ")
322+
case .controls(let value): return value?.joined(separator: " ")
323323
case .current(let value): return value?.rawValue
324-
case .describedby(let value): return value.joined(separator: " ")
324+
case .describedby(let value): return value?.joined(separator: " ")
325325
case .description(let value): return value
326-
case .details(let value): return value.joined(separator: " ")
326+
case .details(let value): return value?.joined(separator: " ")
327327
case .disabled(let value): return unwrap(value)
328328
case .dropeffect(let value): return value?.rawValue
329329
case .errormessage(let value): return value
330330
case .expanded(let value): return value?.rawValue
331-
case .flowto(let value): return value.joined(separator: " ")
331+
case .flowto(let value): return value?.joined(separator: " ")
332332
case .grabbed(let value): return value?.rawValue
333333
case .haspopup(let value): return value?.rawValue
334334
case .hidden(let value): return value?.rawValue
335335
case .invalid(let value): return value?.rawValue
336336
case .keyshortcuts(let value): return value
337337
case .label(let value): return value
338-
case .labelledby(let value): return value.joined(separator: " ")
338+
case .labelledby(let value): return value?.joined(separator: " ")
339339
case .level(let value): return unwrap(value)
340340
case .live(let value): return value?.rawValue
341341
case .modal(let value): return unwrap(value)
342342
case .multiline(let value): return unwrap(value)
343343
case .multiselectable(let value): return unwrap(value)
344344
case .orientation(let value): return value?.rawValue
345-
case .owns(let value): return value.joined(separator: " ")
345+
case .owns(let value): return value?.joined(separator: " ")
346346
case .placeholder(let value): return value
347347
case .posinset(let value): return unwrap(value)
348348
case .pressed(let value): return value?.rawValue

Sources/HTMLKitUtilities/HTMX.swift

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -77,42 +77,12 @@ public extension HTMLElementAttribute {
7777
case "prompt": self = .prompt(string())
7878
case "put": self = .put(string())
7979
case "replaceURL": self = .replaceURL(enumeration())
80-
case "request": // TODO: fix
81-
return nil
82-
/*
83-
let string:String = literal(), values:[Substring] = string.split(separator: ",")
84-
var timeout_string:Substring = values[1][values[1].index(after: values[1].firstIndex(of: ":")!)...]
85-
while timeout_string.first?.isWhitespace ?? false {
86-
timeout_string.removeFirst()
87-
}
88-
let javascript:Bool = values[0].split(separator: ":")[1].hasSuffix("true")
89-
let timeout:String?
90-
if timeout_string.first == "\"" {
91-
timeout_string.removeFirst()
92-
timeout = String(timeout_string[timeout_string.startIndex..<timeout_string.index(before: timeout_string.endIndex)])
93-
} else {
94-
timeout = nil
95-
}
96-
var credentials:String? = nil
97-
var credentials_string:Substring = values[2][values[2].index(after: values[2].firstIndex(of: ":")!)...]
98-
if !credentials_string.hasSuffix("nil") {
99-
while (credentials_string.first?.isWhitespace ?? false) || credentials_string.first == "\"" {
100-
credentials_string.removeFirst()
101-
}
102-
credentials_string.removeLast()
103-
credentials = String(credentials_string)
104-
}
105-
var noHeaders:String? = nil
106-
if !string.hasSuffix("nil") {
107-
var value:Substring = values[3][values[3].index(after: values[3].firstIndex(of: ":")!)...]
108-
while (value.first?.isWhitespace ?? false) || value.first == "\"" {
109-
value.removeFirst()
110-
}
111-
value.removeLast()
112-
noHeaders = (javascript ? "js:" : "") + value
113-
}
114-
self = .request(js: javascript, timeout: timeout, credentials: credentials, noHeaders: noHeaders)
115-
break*/
80+
case "request":
81+
guard let js:Bool = boolean() else { return nil }
82+
let timeout:String? = arguments.get(1)?.expression.string(context: context, key: key)
83+
let credentials:String? = arguments.get(2)?.expression.string(context: context, key: key)
84+
let noHeaders:String? = arguments.get(3)?.expression.string(context: context, key: key)
85+
self = .request(js: js, timeout: timeout, credentials: credentials, noHeaders: noHeaders)
11686
case "sync":
11787
guard let s:String = string() else { return nil }
11888
self = .sync(s, strategy: arguments.last!.expression.enumeration(context: context, key: key, arguments: arguments))

Sources/HTMLKitUtilities/HTMXAttributes.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,12 @@ public extension HTMLElementAttribute.HTMX {
119119
enum Params : HTMLInitializable {
120120
case all
121121
case none
122-
case not([String])
123-
case list([String])
122+
case not([String]?)
123+
case list([String]?)
124124

125125
public init?(context: some MacroExpansionContext, key: String, arguments: LabeledExprListSyntax) {
126126
let expression:ExprSyntax = arguments.first!.expression
127-
func array_string() -> [String] { expression.array_string(context: context, key: key) }
127+
func array_string() -> [String]? { expression.array_string(context: context, key: key) }
128128
switch key {
129129
case "all": self = .all
130130
case "none": self = .none
@@ -147,8 +147,8 @@ public extension HTMLElementAttribute.HTMX {
147147
switch self {
148148
case .all: return "*"
149149
case .none: return "none"
150-
case .not(let list): return "not " + list.joined(separator: ",")
151-
case .list(let list): return list.joined(separator: ",")
150+
case .not(let list): return "not " + (list?.joined(separator: ",") ?? "")
151+
case .list(let list): return list?.joined(separator: ",")
152152
}
153153
}
154154

Sources/HTMLKitUtilities/ParseData.swift

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ import SwiftSyntax
1010
import SwiftSyntaxMacros
1111

1212
public extension HTMLKitUtilities {
13+
// MARK: Escape HTML
14+
static func escapeHTML(expansion: MacroExpansionExprSyntax, context: some MacroExpansionContext) -> String {
15+
return expansion.arguments.children(viewMode: .all).compactMap({
16+
guard let child:LabeledExprSyntax = $0.labeled,
17+
let c:CustomStringConvertible = HTMLKitUtilities.parseInnerHTML(context: context, child: child, lookupFiles: []) else {
18+
return nil
19+
}
20+
return String(describing: c)
21+
}).joined()
22+
}
1323
// MARK: Parse Arguments
1424
static func parseArguments(
1525
context: some MacroExpansionContext,
@@ -94,13 +104,16 @@ public extension HTMLKitUtilities {
94104
return (attributes, trailingSlash)
95105
}
96106

97-
// MARK: Parse innerHTML
107+
// MARK: Parse Inner HTML
98108
static func parseInnerHTML(
99109
context: some MacroExpansionContext,
100110
child: LabeledExprSyntax,
101111
lookupFiles: Set<String>
102112
) -> CustomStringConvertible? {
103113
if let expansion:MacroExpansionExprSyntax = child.expression.macroExpansion {
114+
if expansion.macroName.text == "escapeHTML" {
115+
return escapeHTML(expansion: expansion, context: context).escapingHTML(escapeAttributes: true)
116+
}
104117
return "" // TODO: fix?
105118
} else if let element:HTMLElement = parse_element(context: context, expr: child.expression) {
106119
return element
@@ -342,6 +355,11 @@ package extension SyntaxChildren.Element {
342355
package extension StringLiteralExprSyntax {
343356
var string : String { "\(segments)" }
344357
}
358+
package extension LabeledExprListSyntax {
359+
func get(_ index: Int) -> Element? {
360+
return index < count ? self[self.index(at: index)] : nil
361+
}
362+
}
345363
package extension ExprSyntax {
346364
func string(context: some MacroExpansionContext, key: String) -> String? {
347365
return HTMLKitUtilities.parse_literal_value(context: context, key: key, expression: self, lookupFiles: [])?.value(key: key)
@@ -362,8 +380,8 @@ package extension ExprSyntax {
362380
guard let s:String = HTMLKitUtilities.parse_literal_value(context: context, key: key, expression: self, lookupFiles: [])?.value(key: key) else { return nil }
363381
return Int(s)
364382
}
365-
func array_string(context: some MacroExpansionContext, key: String) -> [String] {
366-
array?.elements.compactMap({ $0.expression.string(context: context, key: key) }) ?? []
383+
func array_string(context: some MacroExpansionContext, key: String) -> [String]? {
384+
array?.elements.compactMap({ $0.expression.string(context: context, key: key) })
367385
}
368386
func dictionary_string_string(context: some MacroExpansionContext, key: String) -> [String:String] {
369387
var d:[String:String] = [:]

0 commit comments

Comments
 (0)