Repository: apple/swift-syntax (swift)

Issue:
Incorrect indentation added to macro that doesn’t start on a new line
The following new test case fails.

```swift
public struct TestMacro: ExpressionMacro {
  public static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax {
    return "1"
  }
}

func testMacroAsArgument() {
  assertMacroExpansion("""
  func test() {
    print(#test)
  }
  """, expandedSource: """
  func test() {
    print(1)
  }
  """, macros: ["test": TestMacro.self])
}
```

Instead, it produces `print( 1)`, adding the indentation of the `print` line to the macro expansion.

Pull request:
Resolves #2473 

---

#### Summary:
- added private function `adjustIndentationOfFreestandingMacro` which extracts the same indentation logic as before, plus removes first line indentation if macro is in middle of any line. This is required as line indentation does not make sense if macro is not present at the beginning of the line.
  - eg. `print(#function)` used to produce `print(  "f(a:_:c:)")` with extra indentation after `(`. Now it should produce `print("f(a:_:c:)")`

- corrected existing unit tests + added similar tests for multi line macro expansion so that we make sure indentation is correct on large macros as well even if they are used in middle of the line 

---

#### Update:
- `adjustIndentationOfFreestandingMacro` adds appropriate indentation to expanded code. Then removes indentation of first line of expanded code (if any). Then adds exact same trivia as # macro, including it's original indentation. 
- Hence removing extra indentation from the expanded code itself and not from leading trivia of # macro node
- Added more test cases with comments with the # macro which were failing earlier

Patch:
diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift
index fff217dbd4c..92bb5352148 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,36 @@ 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 {
+
+  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
+}
+
 private func expandMemberMacro(
   definition: MemberMacro.Type,
   attributeNode: AttributeSyntax,
@@ -1265,9 +1281,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
   }


Test patch:
diff --git a/Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift b/Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift
index 82f75e4f2a4..04148e3b293 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 #""<unknown>""#
+    }
+
+    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,12 +281,182 @@ final class LexicalContextTests: XCTestCase {
       """,
       expandedSource: """
         extension A {
-          static var staticProp: String =   "staticProp"
+          static var staticProp: String = "staticProp"
         }
         """,
       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() {
+    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
+    )
+
+    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() {