@@ -82,21 +82,32 @@ private extension HTMLElement {
8282 isVoid = elementType. isVoid
8383 children = childs. prefix ( childs. count)
8484 }
85- let ( attributes, innerHTML) : ( String , String ) = parse_arguments ( context: context, elementType: elementType, children: children)
86- var string : String = ( tag == " html " ? " <!DOCTYPE html> " : " " ) + " < " + tag + attributes + " > " + innerHTML
85+ let ( attributes, innerHTML, trailingSlash) : ( String , String , Bool ) = parse_arguments ( context: context, elementType: elementType, children: children)
86+ var string : String = ( tag == " html " ? " <!DOCTYPE html> " : " " ) + " < " + tag + attributes
87+ if trailingSlash {
88+ if isVoid {
89+ string += " / "
90+ } else {
91+ context. diagnose ( Diagnostic ( node: macro, message: DiagnosticMsg ( id: " trailingSlashGlobalAttributeMisused " , message: " Trailing Slash global attribute can only be applied to void elements. " ) ) )
92+ return " "
93+ }
94+ }
95+ string += " > " + innerHTML
8796 if !isVoid {
8897 string += " </ " + tag + " > "
8998 }
9099 return string
91100 }
92101 // MARK: Parse Arguments
93- static func parse_arguments( context: some MacroExpansionContext , elementType: HTMLElementType , children: Slice < SyntaxChildren > ) -> ( attributes: String , innerHTML: String ) {
94- var attributes : String = " " , innerHTML : String = " "
102+ static func parse_arguments( context: some MacroExpansionContext , elementType: HTMLElementType , children: Slice < SyntaxChildren > ) -> ( attributes: String , innerHTML: String , trailingSlash : Bool ) {
103+ var attributes : String = " " , innerHTML : String = " " , trailingSlash : Bool = false
95104 for element in children {
96105 if let child: LabeledExprSyntax = element. labeled {
97106 if var key: String = child. label? . text {
98107 if key == " attributes " {
99- for attribute in parse_global_attributes ( context: context, elementType: elementType, array: child. expression. array!. elements) {
108+ let ( attributes_array, ts) : ( [ String ] , Bool ) = parse_global_attributes ( context: context, elementType: elementType, array: child. expression. array!. elements)
109+ trailingSlash = ts
110+ for attribute in attributes_array {
100111 attributes += attribute + " "
101112 }
102113 } else {
@@ -114,57 +125,69 @@ private extension HTMLElement {
114125 }
115126 }
116127 attributes. removeLast ( )
117- return ( attributes, innerHTML)
128+ return ( attributes, innerHTML, trailingSlash )
118129 }
119130 // MARK: Parse Global Attributes
120- static func parse_global_attributes( context: some MacroExpansionContext , elementType: HTMLElementType , array: ArrayElementListSyntax ) -> [ String ] {
121- var keys : Set < String > = [ ] , attributes : [ String ] = [ ]
131+ static func parse_global_attributes( context: some MacroExpansionContext , elementType: HTMLElementType , array: ArrayElementListSyntax ) -> ( attributes : [ String ] , trailingSlash : Bool ) {
132+ var keys : Set < String > = [ ] , attributes : [ String ] = [ ] , trailingSlash : Bool = false
122133 for element in array {
123- let function : FunctionCallExprSyntax = element. expression. functionCall!, first_expression : ExprSyntax = function. arguments. first!. expression
124- var key : String = function. calledExpression. memberAccess!. declName. baseName. text, value : String ! = nil
125- switch key {
126- case " custom " , " data " :
127- var returnType : LiteralReturnType = . string
128- ( value, returnType) = parse_literal_value ( context: context, elementType: elementType, key: key, expression: function. arguments. last!. expression) !
129- if returnType == . string {
130- value. escapeHTML ( escapeAttributes: true )
131- }
132- if key == " custom " {
133- key = first_expression. stringLiteral!. string
134- } else {
135- key += " - \( first_expression. stringLiteral!. string) "
136- }
137- break
138- case " event " :
139- key = " on " + first_expression. memberAccess!. declName. baseName. text
140- if var ( string, returnType) : ( String , LiteralReturnType ) = parse_literal_value ( context: context, elementType: elementType, key: key, expression: function. arguments. last!. expression) {
134+ if let function: FunctionCallExprSyntax = element. expression. functionCall {
135+ let first_expression : ExprSyntax = function. arguments. first!. expression
136+ var key : String = function. calledExpression. memberAccess!. declName. baseName. text, value : String ! = nil
137+ switch key {
138+ case " custom " , " data " :
139+ var returnType : LiteralReturnType = . string
140+ ( value, returnType) = parse_literal_value ( context: context, elementType: elementType, key: key, expression: function. arguments. last!. expression) !
141141 if returnType == . string {
142- string. escapeHTML ( escapeAttributes: true )
142+ value. escapeHTML ( escapeAttributes: true )
143+ }
144+ if key == " custom " {
145+ key = first_expression. stringLiteral!. string
146+ } else {
147+ key += " - \( first_expression. stringLiteral!. string) "
148+ }
149+ break
150+ case " event " :
151+ key = " on " + first_expression. memberAccess!. declName. baseName. text
152+ if var ( string, returnType) : ( String , LiteralReturnType ) = parse_literal_value ( context: context, elementType: elementType, key: key, expression: function. arguments. last!. expression) {
153+ if returnType == . string {
154+ string. escapeHTML ( escapeAttributes: true )
155+ }
156+ value = string
157+ } else {
158+ unallowed_expression ( context: context, node: function. arguments. last!)
159+ return ( [ ] , false )
160+ }
161+ break
162+ default :
163+ if let string: String = parse_attribute ( context: context, elementType: elementType, key: key, expression: first_expression) {
164+ value = string
143165 }
144- value = string
166+ break
167+ }
168+ if key. contains ( " " ) {
169+ context. diagnose ( Diagnostic ( node: first_expression, message: DiagnosticMsg ( id: " spacesNotAllowedInAttributeDeclaration " , message: " Spaces are not allowed in attribute declaration. " ) ) )
170+ } else if let value: String = value {
171+ if keys. contains ( key) {
172+ global_attribute_already_defined ( context: context, attribute: key, node: first_expression)
145173 } else {
146- unallowed_expression ( context : context , node : function . arguments . last! )
147- return [ ]
174+ attributes . append ( key + ( value . isEmpty ? " " : " = \\ \" " + value + " \\ \" " ) )
175+ keys . insert ( key )
148176 }
149- break
150- default :
151- if let string: String = parse_attribute ( context: context, elementType: elementType, key: key, expression: first_expression) {
152- value = string
153- }
154- break
155- }
156- if key. contains ( " " ) {
157- context. diagnose ( Diagnostic ( node: first_expression, message: DiagnosticMsg ( id: " spacesNotAllowedInAttributeDeclaration " , message: " Spaces are not allowed in attribute declaration. " ) ) )
158- } else if let value: String = value {
159- if keys. contains ( key) {
160- context. diagnose ( Diagnostic ( node: first_expression, message: DiagnosticMsg ( id: " globalAttributeAlreadyDefined " , message: " Global attribute \" " + key + " \" is already defined. " ) ) )
177+ }
178+ } else if let member: String = element. expression. memberAccess? . declName. baseName. text, member == " trailingSlash " {
179+ if keys. contains ( member) {
180+ global_attribute_already_defined ( context: context, attribute: member, node: element. expression)
161181 } else {
162- attributes . append ( key + ( value . isEmpty ? " " : " = \\ \" " + value + " \\ \" " ) )
163- keys. insert ( key )
182+ trailingSlash = true
183+ keys. insert ( member )
164184 }
165185 }
166186 }
167- return attributes
187+ return ( attributes, trailingSlash)
188+ }
189+ static func global_attribute_already_defined( context: some MacroExpansionContext , attribute: String , node: some SyntaxProtocol ) {
190+ context. diagnose ( Diagnostic ( node: node, message: DiagnosticMsg ( id: " globalAttributeAlreadyDefined " , message: " Global attribute \" " + attribute + " \" is already defined. " ) ) )
168191 }
169192 // MARK: Parse innerHTML
170193 static func parse_inner_html( context: some MacroExpansionContext , elementType: HTMLElementType , child: LabeledExprSyntax ) -> String ? {
@@ -285,7 +308,8 @@ private extension HTMLElement {
285308 return ( result, . string)
286309 }
287310 if let _: DeclReferenceExprSyntax = expression. as ( DeclReferenceExprSyntax . self) {
288- context. diagnose ( Diagnostic ( node: expression, message: DiagnosticMsg ( id: " runtimeValueNotAllowed " , message: " Runtime value not allowed here. " ) ) )
311+ warn_interpolation ( context: context, node: expression)
312+ return ( " \\ ( \( expression) ) " , . interpolation)
289313 }
290314 return nil
291315 }
0 commit comments