Skip to content

Commit 38d479d

Browse files
improved what node is being warned for unsafeInterpolation compiler warning
1 parent f22ca51 commit 38d479d

File tree

2 files changed

+48
-32
lines changed

2 files changed

+48
-32
lines changed

Sources/HTMLKitMacros/HTMLElement.swift

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,11 @@ private extension HTMLElement {
8888
break
8989
case "event":
9090
key = "on" + key_element.memberAccess!.declName.baseName.text
91-
if var (literalValue, returnType):(String, LiteralReturnType) = parse_literal_value(context: context, elementType: elementType, key: key, argument: function.arguments.last!) {
92-
if returnType == .string {
93-
literalValue.escapeHTML(escapeAttributes: true)
91+
if var result:(String, LiteralReturnType) = parse_literal_value(context: context, elementType: elementType, key: key, argument: function.arguments.last!) {
92+
if result.1 == .string {
93+
result.0.escapeHTML(escapeAttributes: true)
9494
}
95-
value = literalValue
95+
value = result.0
9696
} else {
9797
unallowed_expression(context: context, node: function.arguments.last!)
9898
return []
@@ -164,13 +164,13 @@ private extension HTMLElement {
164164

165165
static func parse_attribute(context: some MacroExpansionContext, elementType: HTMLElementType, key: String, argument: LabeledExprSyntax) -> String? {
166166
let expression:ExprSyntax = argument.expression
167-
if var (string, returnType):(String, LiteralReturnType) = parse_literal_value(context: context, elementType: elementType, key: key, argument: argument) {
168-
switch returnType {
169-
case .boolean: return string.elementsEqual("true") ? "" : nil
167+
if var result:(String, LiteralReturnType) = parse_literal_value(context: context, elementType: elementType, key: key, argument: argument) {
168+
switch result.1 {
169+
case .boolean: return result.0.elementsEqual("true") ? "" : nil
170170
case .string:
171-
string.escapeHTML(escapeAttributes: true)
172-
return string
173-
case .interpolation: return string
171+
result.0.escapeHTML(escapeAttributes: true)
172+
return result.0
173+
case .interpolation: return result.0
174174
}
175175
}
176176
func member(_ value: String) -> String {
@@ -215,22 +215,13 @@ private extension HTMLElement {
215215
if function.calledExpression.as(DeclReferenceExprSyntax.self)?.baseName.text == "StaticString" {
216216
return (function.arguments.first!.expression.stringLiteral!.string, .string)
217217
}
218-
return ("\\(\(function))", .interpolation)
218+
return ("\(function)", .interpolation)
219219
}
220220
}
221221
if let member:MemberAccessExprSyntax = expression.memberAccess {
222222
let decl:String = member.declName.baseName.text
223223
if let _:ExprSyntax = member.base {
224-
/*if let integer:String = base.integerLiteral?.literal.text {
225-
switch decl {
226-
case "description":
227-
return (integer, .integer)
228-
default:
229-
return (integer, .interpolation)
230-
}
231-
} else {*/
232-
return ("\\(\(member))", .interpolation)
233-
//}
224+
return ("\(member)", .interpolation)
234225
} else {
235226
return (HTMLElementAttribute.Extra.htmlValue(enumName: enumName(elementType: elementType, key: key), for: decl), .string)
236227
}
@@ -268,18 +259,18 @@ private extension HTMLElement {
268259
let interpolation:[ExpressionSegmentSyntax] = expression.stringLiteral?.segments.compactMap({ $0.as(ExpressionSegmentSyntax.self) }) ?? []
269260
var remaining_interpolation:Int = interpolation.count
270261
for expr in interpolation {
271-
string = flatten_interpolation(remaining_interpolation: &remaining_interpolation, expr: expr)
262+
string = flatten_interpolation(context: context, remaining_interpolation: &remaining_interpolation, expr: expr)
272263
}
273264
if returnType == .interpolation || remaining_interpolation > 0 {
274265
if !string.contains("\\(") {
275266
string = "\\(" + string + ")"
267+
warn_interpolation(context: context, node: expression)
276268
}
277269
returnType = .interpolation
278-
context.diagnose(Diagnostic(node: expression, message: DiagnosticMsg(id: "unsafeInterpolation", message: "Interpolation may introduce raw HTML.", severity: .warning)))
279270
}
280271
return (string, returnType)
281272
}
282-
static func flatten_interpolation(remaining_interpolation: inout Int, expr: ExpressionSegmentSyntax) -> String {
273+
static func flatten_interpolation(context: some MacroExpansionContext, remaining_interpolation: inout Int, expr: ExpressionSegmentSyntax) -> String {
283274
let expression:ExprSyntax = expr.expressions.first!.expression
284275
var string:String = "\(expr)"
285276
if let stringLiteral:StringLiteralExprSyntax = expression.stringLiteral {
@@ -288,24 +279,39 @@ private extension HTMLElement {
288279
remaining_interpolation = 0
289280
string = segments.map({ $0.as(StringSegmentSyntax.self)!.content.text }).joined()
290281
} else {
291-
var values:[String] = []
282+
string = ""
292283
for segment in segments {
293284
if let literal:String = segment.as(StringSegmentSyntax.self)?.content.text {
294-
values.append(literal)
285+
string += literal
295286
} else if let interpolation:ExpressionSegmentSyntax = segment.as(ExpressionSegmentSyntax.self) {
296-
values.append(flatten_interpolation(remaining_interpolation: &remaining_interpolation, expr: interpolation))
287+
let flattened:String = flatten_interpolation(context: context, remaining_interpolation: &remaining_interpolation, expr: interpolation)
288+
if "\(interpolation)" == flattened {
289+
//string += "\\(\"\(flattened)\".escapingHTML(escapeAttributes: true))"
290+
string += "\(flattened)"
291+
warn_interpolation(context: context, node: interpolation)
292+
} else {
293+
string += flattened
294+
}
297295
} else {
298-
values.append("\(segment)")
296+
//string += "\\(\"\(segment)\".escapingHTML(escapeAttributes: true))"
297+
warn_interpolation(context: context, node: segment)
298+
string += "\(segment)"
299299
}
300300
}
301-
string = values.joined()
302301
}
303302
} else if let fix:String = expression.integerLiteral?.literal.text ?? expression.floatLiteral?.literal.text {
304-
remaining_interpolation -= string.ranges(of: "\(expr)").count
305-
string.replace("\(expr)", with: fix)
303+
let target:String = "\(expr)"
304+
remaining_interpolation -= string.ranges(of: target).count
305+
string.replace(target, with: fix)
306+
} else {
307+
//string = "\\(\"\(string)\".escapingHTML(escapeAttributes: true))"
308+
warn_interpolation(context: context, node: expr)
306309
}
307310
return string
308311
}
312+
static func warn_interpolation(context: some MacroExpansionContext, node: some SyntaxProtocol) {
313+
context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "unsafeInterpolation", message: "Interpolation may introduce raw HTML.", severity: .warning)))
314+
}
309315
}
310316

311317
enum LiteralReturnType {

Tests/HTMLKitTests/HTMLKitTests.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ struct HTMLKitTests {
1212
@Test func escape_html() {
1313
let unescaped:String = "<!DOCTYPE html><html>Test</html>"
1414
let escaped:String = "&lt;!DOCTYPE html&gt;&lt;html&gt;Test&lt;/html&gt;"
15-
let expected_result:String = "<p>\(escaped)</p>"
15+
var expected_result:String = "<p>\(escaped)</p>"
1616

1717
var string:String = #p("<!DOCTYPE html><html>Test</html>")
1818
#expect(string == expected_result)
@@ -25,6 +25,16 @@ struct HTMLKitTests {
2525

2626
string = #p("\(unescaped.escapingHTML(escapeAttributes: false))")
2727
#expect(string == expected_result)
28+
29+
expected_result = "<div title=\"&lt;p&gt;\">&lt;p&gt;&lt;/p&gt;</div>"
30+
string = #div(attributes: [.title(StaticString("<p>"))], StaticString("<p></p>")).description
31+
#expect(string == expected_result)
32+
33+
string = #div(attributes: [.title("<p>")], StaticString("<p></p>")).description
34+
#expect(string == expected_result)
35+
36+
string = #div(attributes: [.title("<p>")], "<p></p>")
37+
#expect(string == expected_result)
2838
}
2939
}
3040

0 commit comments

Comments
 (0)