Skip to content

Commit 76ed3f1

Browse files
can now use multiline strings for values
- minor unit test updates
1 parent 5a60786 commit 76ed3f1

File tree

14 files changed

+170
-123
lines changed

14 files changed

+170
-123
lines changed

Sources/HTMLAttributes/HTMLAttributes+Extra.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ extension HTMLAttribute.Extra {
569569
case "showPopover": self = .showPopover
570570
case "hidePopover": self = .hidePopover
571571
case "togglePopover": self = .togglePopover
572-
case "custom": self = .custom(context.expression!.stringLiteral!.string)
572+
case "custom": self = .custom(context.expression!.stringLiteral!.string(encoding: context.encoding))
573573
default: return nil
574574
}
575575
}
@@ -665,7 +665,7 @@ extension HTMLAttribute.Extra {
665665
public init?(context: HTMLExpansionContext) {
666666
switch context.key {
667667
case "empty": self = .empty
668-
case "filename": self = .filename(context.expression!.stringLiteral!.string)
668+
case "filename": self = .filename(context.expression!.stringLiteral!.string(encoding: context.encoding))
669669
default: return nil
670670
}
671671
}

Sources/HTMLKit/HTMLKit.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public macro html<T: CustomStringConvertible>(
3838
_ innerHTML: CustomStringConvertible & Sendable...
3939
) -> T = #externalMacro(module: "HTMLKitMacros", type: "HTMLElementMacro")
4040

41+
// MARK: Unchecked
4142
/// Same as `#html` but ignoring compiler warnings.
4243
@freestanding(expression)
4344
public macro uncheckedHTML<T: CustomStringConvertible>(

Sources/HTMLKitParse/InterpolationLookup.swift

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#if canImport(Foundation)
99
import Foundation
10+
import HTMLKitUtilities
1011
import SwiftDiagnostics
1112
import SwiftSyntaxMacros
1213
import SwiftParser
@@ -15,23 +16,23 @@ import SwiftSyntax
1516
enum InterpolationLookup {
1617
private static var cached:[String:CodeBlockItemListSyntax] = [:]
1718

18-
static func find(context: some MacroExpansionContext, _ node: some ExprSyntaxProtocol, files: Set<String>) -> String? {
19-
guard !files.isEmpty, let item:Item = item(node) else { return nil }
19+
static func find(context: HTMLExpansionContext, _ node: some ExprSyntaxProtocol, files: Set<String>) -> String? {
20+
guard !files.isEmpty, let item:Item = item(context: context, node) else { return nil }
2021
for file in files {
2122
if cached[file] == nil {
2223
if let string:String = try? String.init(contentsOfFile: file, encoding: .utf8) {
2324
let parsed:CodeBlockItemListSyntax = Parser.parse(source: string).statements
2425
cached[file] = parsed
2526
} else {
26-
context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "fileNotFound", message: "Could not find file (\(file)) on disk, or was denied disk access (file access is always denied on macOS due to the macro being in a sandbox).", severity: .warning)))
27+
context.context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "fileNotFound", message: "Could not find file (\(file)) on disk, or was denied disk access (file access is always denied on macOS due to the macro being in a sandbox).", severity: .warning)))
2728
}
2829
}
2930
}
3031
//print("InterpolationLookup;find;item=\(item)")
3132
switch item {
3233
case .literal(let tokens):
3334
for (_, statements) in cached {
34-
if let flattened:String = flatten(tokens, statements: statements) {
35+
if let flattened:String = flatten(context: context, tokens: tokens, statements: statements) {
3536
return flattened
3637
}
3738
}
@@ -42,15 +43,15 @@ enum InterpolationLookup {
4243
}
4344
}
4445

45-
private static func item(_ node: some ExprSyntaxProtocol) -> Item? {
46+
private static func item(context: HTMLExpansionContext, _ node: some ExprSyntaxProtocol) -> Item? {
4647
if let function:FunctionCallExprSyntax = node.functionCall {
4748
var array:[String] = []
4849
if let member:MemberAccessExprSyntax = function.calledExpression.memberAccess {
4950
array.append(contentsOf: test(member))
5051
}
5152
var parameters:[String] = []
5253
for argument in function.arguments {
53-
if let string:String = argument.expression.stringLiteral?.string {
54+
if let string:String = argument.expression.stringLiteral?.string(encoding: context.encoding) {
5455
parameters.append(string)
5556
}
5657
}
@@ -80,7 +81,7 @@ enum InterpolationLookup {
8081
}
8182
// MARK: Flatten
8283
private extension InterpolationLookup {
83-
static func flatten(_ tokens: [String], statements: CodeBlockItemListSyntax) -> String? {
84+
static func flatten(context: HTMLExpansionContext, tokens: [String], statements: CodeBlockItemListSyntax) -> String? {
8485
for statement in statements {
8586
var index:Int = 0
8687
let item = statement.item
@@ -90,8 +91,8 @@ private extension InterpolationLookup {
9091
}
9192
for member in ext.memberBlock.members {
9293
if let string:String = parse_function(syntax: member.decl, tokens: tokens, index: index)
93-
?? parse_enumeration(syntax: member.decl, tokens: tokens, index: index)
94-
?? parse_variable(syntax: member.decl, tokens: tokens, index: index) {
94+
?? parse_enumeration(context: context, syntax: member.decl, tokens: tokens, index: index)
95+
?? parse_variable(context: context, syntax: member.decl, tokens: tokens, index: index) {
9596
return string
9697
}
9798
}
@@ -104,9 +105,9 @@ private extension InterpolationLookup {
104105
index -= 1
105106
}
106107
}
107-
} else if let enumeration:String = parse_enumeration(syntax: item, tokens: tokens, index: index) {
108+
} else if let enumeration:String = parse_enumeration(context: context, syntax: item, tokens: tokens, index: index) {
108109
return enumeration
109-
} else if let variable:String = parse_variable(syntax: item, tokens: tokens, index: index) {
110+
} else if let variable:String = parse_variable(context: context, syntax: item, tokens: tokens, index: index) {
110111
return variable
111112
}
112113
}
@@ -118,7 +119,7 @@ private extension InterpolationLookup {
118119
return nil
119120
}
120121
// MARK: Parse enumeration
121-
static func parse_enumeration(syntax: some SyntaxProtocol, tokens: [String], index: Int) -> String? {
122+
static func parse_enumeration(context: HTMLExpansionContext, syntax: some SyntaxProtocol, tokens: [String], index: Int) -> String? {
122123
let allowed_inheritances:Set<String?> = ["String", "Int", "Double", "Float"]
123124
guard let enumeration:EnumDeclSyntax = syntax.enumeration,
124125
enumeration.name.text == tokens[index]
@@ -138,7 +139,7 @@ private extension InterpolationLookup {
138139
return case_name
139140
}
140141
switch value_type {
141-
case "String": return enum_case.rawValue?.value.stringLiteral!.string ?? case_name
142+
case "String": return enum_case.rawValue?.value.stringLiteral!.string(encoding: context.encoding) ?? case_name
142143
case "Int": return enum_case.rawValue?.value.integerLiteral!.literal.text ?? case_name
143144
case "Double", "Float": return enum_case.rawValue?.value.floatLiteral!.literal.text ?? case_name
144145
default:
@@ -152,11 +153,11 @@ private extension InterpolationLookup {
152153
return nil
153154
}
154155
// MARK: Parse variable
155-
static func parse_variable(syntax: some SyntaxProtocol, tokens: [String], index: Int) -> String? {
156+
static func parse_variable(context: HTMLExpansionContext, syntax: some SyntaxProtocol, tokens: [String], index: Int) -> String? {
156157
guard let variable:VariableDeclSyntax = syntax.variableDecl else { return nil }
157158
for binding in variable.bindings {
158159
if binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text == tokens[index], let initializer:InitializerClauseSyntax = binding.initializer {
159-
return initializer.value.stringLiteral?.string
160+
return initializer.value.stringLiteral?.string(encoding: context.encoding)
160161
?? initializer.value.integerLiteral?.literal.text
161162
?? initializer.value.floatLiteral?.literal.text
162163
}

Sources/HTMLKitParse/ParseData.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ extension HTMLKitUtilities {
100100
if key == "encoding" {
101101
context.encoding = parseEncoding(expression: child.expression) ?? .string
102102
} else if key == "lookupFiles" {
103-
context.lookupFiles = Set(child.expression.array!.elements.compactMap({ $0.expression.stringLiteral?.string }))
103+
context.lookupFiles = Set(child.expression.array!.elements.compactMap({ $0.expression.stringLiteral?.string(encoding: context.encoding) }))
104104
} else if key == "attributes" {
105105
(global_attributes, trailingSlash) = parseGlobalAttributes(context: context, array: child.expression.array!.elements)
106106
} else {
@@ -142,11 +142,11 @@ extension HTMLKitUtilities {
142142
} else if let function:FunctionCallExprSyntax = expression.functionCall {
143143
switch function.calledExpression.as(MemberAccessExprSyntax.self)?.declName.baseName.text {
144144
case "custom":
145-
guard let logic:String = function.arguments.first?.expression.stringLiteral?.string else { break }
145+
guard let logic:String = function.arguments.first?.expression.stringLiteral?.string(encoding: .string) else { break }
146146
if function.arguments.count == 1 {
147147
return .custom(logic)
148148
} else {
149-
return .custom(logic, stringDelimiter: function.arguments.last!.expression.stringLiteral!.string)
149+
return .custom(logic, stringDelimiter: function.arguments.last!.expression.stringLiteral!.string(encoding: .string))
150150
}
151151
default:
152152
break

Sources/HTMLKitParse/ParseLiteral.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ extension HTMLKitUtilities {
107107
let segments:StringLiteralSegmentListSyntax = stringLiteral.segments
108108
if segments.count(where: { $0.is(StringSegmentSyntax.self) }) == segments.count {
109109
remaining_interpolation -= 1
110-
values.append(create(stringLiteral.string))
110+
values.append(create(stringLiteral.string(encoding: context.encoding)))
111111
} else {
112112
for segment in segments {
113113
if let literal:String = segment.as(StringSegmentSyntax.self)?.content.text {
@@ -143,7 +143,7 @@ extension HTMLKitUtilities {
143143
return nil
144144
}
145145
if let stringLiteral:StringLiteralExprSyntax = expression.stringLiteral {
146-
let string:String = stringLiteral.string
146+
let string:String = stringLiteral.string(encoding: context.encoding)
147147
if stringLiteral.segments.count(where: { $0.is(ExpressionSegmentSyntax.self) }) == 0 {
148148
return .string(string)
149149
} else {
@@ -154,7 +154,7 @@ extension HTMLKitUtilities {
154154
if let decl:String = function.calledExpression.declRef?.baseName.text {
155155
switch decl {
156156
case "StaticString":
157-
let string:String = function.arguments.first!.expression.stringLiteral!.string
157+
let string:String = function.arguments.first!.expression.stringLiteral!.string(encoding: context.encoding)
158158
return .string(string)
159159
default:
160160
break

Sources/HTMLKitUtilities/HTMLKitUtilities.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,19 @@ extension SyntaxChildren.Element {
7676
package var labeled : LabeledExprSyntax? { self.as(LabeledExprSyntax.self) }
7777
}
7878
extension StringLiteralExprSyntax {
79-
package var string : String { "\(segments)" }
79+
package func string(encoding: HTMLEncoding) -> String {
80+
if openingQuote.debugDescription.hasPrefix("multilineStringQuote") {
81+
var value:String = segments.compactMap({ $0.as(StringSegmentSyntax.self)?.content.text }).joined()
82+
switch encoding {
83+
case .string:
84+
value.replace("\n", with: "\\n")
85+
default:
86+
break
87+
}
88+
return value
89+
}
90+
return "\(segments)"
91+
}
8092
}
8193
extension LabeledExprListSyntax {
8294
package func get(_ index: Int) -> Element? {

Sources/HTMX/HTMX+Attributes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ extension HTMXAttribute {
205205
switch context.key {
206206
case "true": self = .true
207207
case "false": self = .false
208-
case "url": self = .url(context.expression!.stringLiteral!.string)
208+
case "url": self = .url(context.expression!.stringLiteral!.string(encoding: context.encoding))
209209
default: return nil
210210
}
211211
}

Tests/HTMLKitTests/AttributeTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ struct AttributeTests {
4444
}
4545

4646
// MARK: class
47-
@Test func class_attribute() {
47+
@Test func classAttribute() {
4848
let string:StaticString = #html(a(attributes: [.class(["womp", "donk", "g2-esports"])]))
4949
#expect(string == "<a class=\"womp donk g2-esports\"></a>")
5050
}
@@ -65,7 +65,7 @@ struct AttributeTests {
6565
}
6666

6767
// MARK: custom
68-
@Test func custom_attribute() {
68+
@Test func customAttribute() {
6969
var string:StaticString = #html(div(attributes: [.custom("potofgold", "north")]))
7070
#expect(string == "<div potofgold=\"north\"></div>")
7171

0 commit comments

Comments
 (0)