From 32c666293ee7c739d9975d3c5f544f148677c10b Mon Sep 17 00:00:00 2001 From: Vinayak Vanjari Date: Mon, 12 Feb 2024 19:28:15 +0400 Subject: [PATCH 1/4] adjust Indentation Of Freestanding Macro --- .../MacroSystem.swift | 39 +++--- .../LexicalContextTests.swift | 120 +++++++++++++++++- 2 files changed, 142 insertions(+), 17 deletions(-) diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift index fff217dbd4c..b8acddea9ef 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift @@ -78,10 +78,8 @@ private func expandFreestandingMemberDeclList( return nil } - let indentedSource = - expanded - .indented(by: node.indentationOfFirstLine) - .wrappingInTrivia(from: node) + let indentedSource = adjustIndentationOfFreestandingMacro(expandedCode: expanded, node: node) + return "\(raw: indentedSource)" } @@ -103,13 +101,8 @@ private func expandFreestandingCodeItemList( return nil } - // The macro expansion just provides an expansion for the content. - // We need to make sure that we aren’t dropping the trivia before and after - // the expansion. - let indentedSource = - expanded - .indented(by: node.indentationOfFirstLine) - .wrappingInTrivia(from: node) + let indentedSource = adjustIndentationOfFreestandingMacro(expandedCode: expanded, node: node) + return "\(raw: indentedSource)" } @@ -131,13 +124,29 @@ private func expandFreestandingExpr( return nil } - let indentedSource = - expanded - .indented(by: node.indentationOfFirstLine) - .wrappingInTrivia(from: node) + let indentedSource = adjustIndentationOfFreestandingMacro(expandedCode: expanded, node: node) + return "\(raw: indentedSource)" } +/// Adds the appropriate indentation on expanded code even if it's multi line. +/// Makes sure original macro expression's trivia is maintained by adding it to expanded code. +private func adjustIndentationOfFreestandingMacro(expandedCode: String, node: some FreestandingMacroExpansionSyntax) -> String { + let indentationOfFirstLine = node.indentationOfFirstLine + + var indentedSource = + expandedCode + .indented(by: indentationOfFirstLine) + .wrappingInTrivia(from: node) + + // if the experssion started in middle of the line, then remove indentation of the first line + if !node.leadingTrivia.contains(where: \.isNewline) { + indentedSource.removeFirst(indentationOfFirstLine.sourceLength.utf8Length) + } + + return indentedSource +} + private func expandMemberMacro( definition: MemberMacro.Type, attributeNode: AttributeSyntax, diff --git a/Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift b/Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift index 82f75e4f2a4..c8656e174bd 100644 --- a/Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift +++ b/Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift @@ -173,6 +173,24 @@ public struct FunctionMacro: ExpressionMacro { } } +public struct MultilineFunctionMacro: ExpressionMacro { + public static func expansion< + Node: FreestandingMacroExpansionSyntax, + Context: MacroExpansionContext + >( + of node: Node, + in context: Context + ) -> ExprSyntax { + guard let lexicalContext = context.lexicalContext.first, + let name = lexicalContext.functionName(in: context) + else { + return #""""# + } + + return ExprSyntax("{\n\(literal: name)\n}") + } +} + public struct AllLexicalContextsMacro: DeclarationMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, @@ -194,7 +212,7 @@ final class LexicalContextTests: XCTestCase { """, expandedSource: """ func f(a: Int, _: Double, c: Int) { - print( "f(a:_:c:)") + print("f(a:_:c:)") } """, macros: ["function": FunctionMacro.self], @@ -263,7 +281,7 @@ final class LexicalContextTests: XCTestCase { """, expandedSource: """ extension A { - static var staticProp: String = "staticProp" + static var staticProp: String = "staticProp" } """, macros: ["function": FunctionMacro.self], @@ -271,6 +289,104 @@ final class LexicalContextTests: XCTestCase { ) } + func testPoundMultilineFunction() { + assertMacroExpansion( + """ + func f(a: Int, _: Double, c: Int) { + print(#function) + } + """, + expandedSource: """ + func f(a: Int, _: Double, c: Int) { + print({ + "f(a:_:c:)" + }) + } + """, + macros: ["function": MultilineFunctionMacro.self], + indentationWidth: indentationWidth + ) + + assertMacroExpansion( + """ + struct X { + init(from: String) { + #function + } + + subscript(a: Int) -> String { + #function + } + + subscript(a a: Int) -> String { + #function + } + } + """, + expandedSource: """ + struct X { + init(from: String) { + { + "init(from:)" + } + } + + subscript(a: Int) -> String { + { + "subscript(_:)" + } + } + + subscript(a a: Int) -> String { + { + "subscript(a:)" + } + } + } + """, + macros: ["function": MultilineFunctionMacro.self], + indentationWidth: indentationWidth + ) + + assertMacroExpansion( + """ + var computed: String { + get { + #function + } + } + """, + expandedSource: """ + var computed: String { + get { + { + "computed" + } + } + } + """, + macros: ["function": MultilineFunctionMacro.self], + indentationWidth: indentationWidth + ) + + assertMacroExpansion( + """ + extension A { + static var staticProp: String = #function + } + """, + expandedSource: """ + extension A { + static var staticProp: String = { + "staticProp" + } + } + """, + macros: ["function": MultilineFunctionMacro.self], + indentationWidth: indentationWidth + ) + } + func testAllLexicalContexts() { assertMacroExpansion( """ From b508e4b699c20a24e9d26789e75130fb90a62460 Mon Sep 17 00:00:00 2001 From: Vinayak Vanjari Date: Wed, 14 Feb 2024 20:00:02 +0400 Subject: [PATCH 2/4] fix: comments with # macros should also be appropriately indented --- .../MacroSystem.swift | 10 ++- .../LexicalContextTests.swift | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift index b8acddea9ef..51573df586b 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift @@ -137,13 +137,13 @@ private func adjustIndentationOfFreestandingMacro(expandedCode: String, node: so var indentedSource = expandedCode .indented(by: indentationOfFirstLine) - .wrappingInTrivia(from: node) - // if the experssion started in middle of the line, then remove indentation of the first line - if !node.leadingTrivia.contains(where: \.isNewline) { + if indentedSource.count >= indentationOfFirstLine.sourceLength.utf8Length { indentedSource.removeFirst(indentationOfFirstLine.sourceLength.utf8Length) } + indentedSource = indentedSource.wrappingInTrivia(from: node) + return indentedSource } @@ -1274,9 +1274,7 @@ private extension String { /// user should think about it as just replacing the `#...` expression without /// any trivia. func wrappingInTrivia(from node: some SyntaxProtocol) -> String { - // We need to remove the indentation from the last line because the macro - // expansion buffer already contains the indentation. - return node.leadingTrivia.removingIndentationOnLastLine.description + return node.leadingTrivia.description + self + node.trailingTrivia.description } diff --git a/Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift b/Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift index c8656e174bd..04148e3b293 100644 --- a/Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift +++ b/Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift @@ -287,6 +287,40 @@ final class LexicalContextTests: XCTestCase { macros: ["function": FunctionMacro.self], indentationWidth: indentationWidth ) + + assertMacroExpansion( + """ + func f(a: Int, _: Double, c: Int) { + print(/*comment*/#function) + } + """, + expandedSource: """ + func f(a: Int, _: Double, c: Int) { + print(/*comment*/"f(a:_:c:)") + } + """, + macros: ["function": FunctionMacro.self], + indentationWidth: indentationWidth + ) + + assertMacroExpansion( + """ + var computed: String { + get { + /*comment*/#function + } + } + """, + expandedSource: """ + var computed: String { + get { + /*comment*/"computed" + } + } + """, + macros: ["function": FunctionMacro.self], + indentationWidth: indentationWidth + ) } func testPoundMultilineFunction() { @@ -385,6 +419,44 @@ final class LexicalContextTests: XCTestCase { macros: ["function": MultilineFunctionMacro.self], indentationWidth: indentationWidth ) + + assertMacroExpansion( + """ + func f(a: Int, _: Double, c: Int) { + print(/*comment*/#function) + } + """, + expandedSource: """ + func f(a: Int, _: Double, c: Int) { + print(/*comment*/{ + "f(a:_:c:)" + }) + } + """, + macros: ["function": MultilineFunctionMacro.self], + indentationWidth: indentationWidth + ) + + assertMacroExpansion( + """ + var computed: String { + get { + /*comment*/#function // another comment + } + } + """, + expandedSource: """ + var computed: String { + get { + /*comment*/{ + "computed" + } // another comment + } + } + """, + macros: ["function": MultilineFunctionMacro.self], + indentationWidth: indentationWidth + ) } func testAllLexicalContexts() { From cb7a6a0985f907b1236c11bc4ad49fdbc76970ef Mon Sep 17 00:00:00 2001 From: Vinayak Vanjari Date: Mon, 19 Feb 2024 22:25:26 +0400 Subject: [PATCH 3/4] improved readability & added explanation comment --- .../MacroSystem.swift | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift index 51573df586b..7a27d3c48ac 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift @@ -132,16 +132,23 @@ private func expandFreestandingExpr( /// Adds the appropriate indentation on expanded code even if it's multi line. /// Makes sure original macro expression's trivia is maintained by adding it to expanded code. private func adjustIndentationOfFreestandingMacro(expandedCode: String, node: some FreestandingMacroExpansionSyntax) -> String { - let indentationOfFirstLine = node.indentationOfFirstLine - - var indentedSource = - expandedCode - .indented(by: indentationOfFirstLine) - - if indentedSource.count >= indentationOfFirstLine.sourceLength.utf8Length { - indentedSource.removeFirst(indentationOfFirstLine.sourceLength.utf8Length) + + if expandedCode.isEmpty { + return expandedCode.wrappingInTrivia(from: node) } - + + let indentationOfFirstLine = node.indentationOfFirstLine + let indentLength = indentationOfFirstLine.sourceLength.utf8Length + + // we are doing 3 step adjustment here + // step 1: add indentation to each line of expanded code + // step 2: remove indentation from first line of expaned code + // step 3: wrap the expanded code into macro expression's trivia. This trivia will contain appropriate existing + // indentation. Note that if macro expression occurs in middle of the line then there will be no indentation or extra space. + // Hence we are doing step 2 + + var indentedSource = expandedCode.indented(by: indentationOfFirstLine) + indentedSource.removeFirst(indentLength) indentedSource = indentedSource.wrappingInTrivia(from: node) return indentedSource From aee75d5894a3ed94e18d43294cfc57d7a5142ebb Mon Sep 17 00:00:00 2001 From: Vinayak Vanjari Date: Tue, 20 Feb 2024 08:20:37 +0400 Subject: [PATCH 4/4] format fixes --- Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift index 7a27d3c48ac..92bb5352148 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift @@ -132,21 +132,21 @@ private func expandFreestandingExpr( /// Adds the appropriate indentation on expanded code even if it's multi line. /// Makes sure original macro expression's trivia is maintained by adding it to expanded code. private func adjustIndentationOfFreestandingMacro(expandedCode: String, node: some FreestandingMacroExpansionSyntax) -> String { - + if expandedCode.isEmpty { return expandedCode.wrappingInTrivia(from: node) } - + let indentationOfFirstLine = node.indentationOfFirstLine let indentLength = indentationOfFirstLine.sourceLength.utf8Length - + // we are doing 3 step adjustment here // step 1: add indentation to each line of expanded code // step 2: remove indentation from first line of expaned code // step 3: wrap the expanded code into macro expression's trivia. This trivia will contain appropriate existing // indentation. Note that if macro expression occurs in middle of the line then there will be no indentation or extra space. // Hence we are doing step 2 - + var indentedSource = expandedCode.indented(by: indentationOfFirstLine) indentedSource.removeFirst(indentLength) indentedSource = indentedSource.wrappingInTrivia(from: node)