Skip to content

Commit 8802f12

Browse files
authored
Merge pull request #2493 from vinu-vanjari/macro-adjustIndentationOfFreestandingMacro
adjust Indentation Of Freestanding Macro
2 parents c7b2500 + aee75d5 commit 8802f12

File tree

2 files changed

+222
-20
lines changed

2 files changed

+222
-20
lines changed

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,8 @@ private func expandFreestandingMemberDeclList(
9393
return nil
9494
}
9595

96-
let indentedSource =
97-
expanded
98-
.indented(by: node.indentationOfFirstLine)
99-
.wrappingInTrivia(from: node)
96+
let indentedSource = adjustIndentationOfFreestandingMacro(expandedCode: expanded, node: node)
97+
10098
return "\(raw: indentedSource)"
10199
}
102100

@@ -118,13 +116,8 @@ private func expandFreestandingCodeItemList(
118116
return nil
119117
}
120118

121-
// The macro expansion just provides an expansion for the content.
122-
// We need to make sure that we aren’t dropping the trivia before and after
123-
// the expansion.
124-
let indentedSource =
125-
expanded
126-
.indented(by: node.indentationOfFirstLine)
127-
.wrappingInTrivia(from: node)
119+
let indentedSource = adjustIndentationOfFreestandingMacro(expandedCode: expanded, node: node)
120+
128121
return "\(raw: indentedSource)"
129122
}
130123

@@ -146,13 +139,36 @@ private func expandFreestandingExpr(
146139
return nil
147140
}
148141

149-
let indentedSource =
150-
expanded
151-
.indented(by: node.indentationOfFirstLine)
152-
.wrappingInTrivia(from: node)
142+
let indentedSource = adjustIndentationOfFreestandingMacro(expandedCode: expanded, node: node)
143+
153144
return "\(raw: indentedSource)"
154145
}
155146

147+
/// Adds the appropriate indentation on expanded code even if it's multi line.
148+
/// Makes sure original macro expression's trivia is maintained by adding it to expanded code.
149+
private func adjustIndentationOfFreestandingMacro(expandedCode: String, node: some FreestandingMacroExpansionSyntax) -> String {
150+
151+
if expandedCode.isEmpty {
152+
return expandedCode.wrappingInTrivia(from: node)
153+
}
154+
155+
let indentationOfFirstLine = node.indentationOfFirstLine
156+
let indentLength = indentationOfFirstLine.sourceLength.utf8Length
157+
158+
// we are doing 3 step adjustment here
159+
// step 1: add indentation to each line of expanded code
160+
// step 2: remove indentation from first line of expaned code
161+
// step 3: wrap the expanded code into macro expression's trivia. This trivia will contain appropriate existing
162+
// indentation. Note that if macro expression occurs in middle of the line then there will be no indentation or extra space.
163+
// Hence we are doing step 2
164+
165+
var indentedSource = expandedCode.indented(by: indentationOfFirstLine)
166+
indentedSource.removeFirst(indentLength)
167+
indentedSource = indentedSource.wrappingInTrivia(from: node)
168+
169+
return indentedSource
170+
}
171+
156172
private func expandMemberMacro(
157173
definition: MemberMacro.Type,
158174
attributeNode: AttributeSyntax,
@@ -1284,9 +1300,7 @@ private extension String {
12841300
/// user should think about it as just replacing the `#...` expression without
12851301
/// any trivia.
12861302
func wrappingInTrivia(from node: some SyntaxProtocol) -> String {
1287-
// We need to remove the indentation from the last line because the macro
1288-
// expansion buffer already contains the indentation.
1289-
return node.leadingTrivia.removingIndentationOnLastLine.description
1303+
return node.leadingTrivia.description
12901304
+ self
12911305
+ node.trailingTrivia.description
12921306
}

Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift

Lines changed: 190 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,24 @@ public struct FunctionMacro: ExpressionMacro {
173173
}
174174
}
175175

176+
public struct MultilineFunctionMacro: ExpressionMacro {
177+
public static func expansion<
178+
Node: FreestandingMacroExpansionSyntax,
179+
Context: MacroExpansionContext
180+
>(
181+
of node: Node,
182+
in context: Context
183+
) -> ExprSyntax {
184+
guard let lexicalContext = context.lexicalContext.first,
185+
let name = lexicalContext.functionName(in: context)
186+
else {
187+
return #""<unknown>""#
188+
}
189+
190+
return ExprSyntax("{\n\(literal: name)\n}")
191+
}
192+
}
193+
176194
public struct AllLexicalContextsMacro: DeclarationMacro {
177195
public static func expansion(
178196
of node: some FreestandingMacroExpansionSyntax,
@@ -194,7 +212,7 @@ final class LexicalContextTests: XCTestCase {
194212
""",
195213
expandedSource: """
196214
func f(a: Int, _: Double, c: Int) {
197-
print( "f(a:_:c:)")
215+
print("f(a:_:c:)")
198216
}
199217
""",
200218
macros: ["function": FunctionMacro.self],
@@ -263,12 +281,182 @@ final class LexicalContextTests: XCTestCase {
263281
""",
264282
expandedSource: """
265283
extension A {
266-
static var staticProp: String = "staticProp"
284+
static var staticProp: String = "staticProp"
267285
}
268286
""",
269287
macros: ["function": FunctionMacro.self],
270288
indentationWidth: indentationWidth
271289
)
290+
291+
assertMacroExpansion(
292+
"""
293+
func f(a: Int, _: Double, c: Int) {
294+
print(/*comment*/#function)
295+
}
296+
""",
297+
expandedSource: """
298+
func f(a: Int, _: Double, c: Int) {
299+
print(/*comment*/"f(a:_:c:)")
300+
}
301+
""",
302+
macros: ["function": FunctionMacro.self],
303+
indentationWidth: indentationWidth
304+
)
305+
306+
assertMacroExpansion(
307+
"""
308+
var computed: String {
309+
get {
310+
/*comment*/#function
311+
}
312+
}
313+
""",
314+
expandedSource: """
315+
var computed: String {
316+
get {
317+
/*comment*/"computed"
318+
}
319+
}
320+
""",
321+
macros: ["function": FunctionMacro.self],
322+
indentationWidth: indentationWidth
323+
)
324+
}
325+
326+
func testPoundMultilineFunction() {
327+
assertMacroExpansion(
328+
"""
329+
func f(a: Int, _: Double, c: Int) {
330+
print(#function)
331+
}
332+
""",
333+
expandedSource: """
334+
func f(a: Int, _: Double, c: Int) {
335+
print({
336+
"f(a:_:c:)"
337+
})
338+
}
339+
""",
340+
macros: ["function": MultilineFunctionMacro.self],
341+
indentationWidth: indentationWidth
342+
)
343+
344+
assertMacroExpansion(
345+
"""
346+
struct X {
347+
init(from: String) {
348+
#function
349+
}
350+
351+
subscript(a: Int) -> String {
352+
#function
353+
}
354+
355+
subscript(a a: Int) -> String {
356+
#function
357+
}
358+
}
359+
""",
360+
expandedSource: """
361+
struct X {
362+
init(from: String) {
363+
{
364+
"init(from:)"
365+
}
366+
}
367+
368+
subscript(a: Int) -> String {
369+
{
370+
"subscript(_:)"
371+
}
372+
}
373+
374+
subscript(a a: Int) -> String {
375+
{
376+
"subscript(a:)"
377+
}
378+
}
379+
}
380+
""",
381+
macros: ["function": MultilineFunctionMacro.self],
382+
indentationWidth: indentationWidth
383+
)
384+
385+
assertMacroExpansion(
386+
"""
387+
var computed: String {
388+
get {
389+
#function
390+
}
391+
}
392+
""",
393+
expandedSource: """
394+
var computed: String {
395+
get {
396+
{
397+
"computed"
398+
}
399+
}
400+
}
401+
""",
402+
macros: ["function": MultilineFunctionMacro.self],
403+
indentationWidth: indentationWidth
404+
)
405+
406+
assertMacroExpansion(
407+
"""
408+
extension A {
409+
static var staticProp: String = #function
410+
}
411+
""",
412+
expandedSource: """
413+
extension A {
414+
static var staticProp: String = {
415+
"staticProp"
416+
}
417+
}
418+
""",
419+
macros: ["function": MultilineFunctionMacro.self],
420+
indentationWidth: indentationWidth
421+
)
422+
423+
assertMacroExpansion(
424+
"""
425+
func f(a: Int, _: Double, c: Int) {
426+
print(/*comment*/#function)
427+
}
428+
""",
429+
expandedSource: """
430+
func f(a: Int, _: Double, c: Int) {
431+
print(/*comment*/{
432+
"f(a:_:c:)"
433+
})
434+
}
435+
""",
436+
macros: ["function": MultilineFunctionMacro.self],
437+
indentationWidth: indentationWidth
438+
)
439+
440+
assertMacroExpansion(
441+
"""
442+
var computed: String {
443+
get {
444+
/*comment*/#function // another comment
445+
}
446+
}
447+
""",
448+
expandedSource: """
449+
var computed: String {
450+
get {
451+
/*comment*/{
452+
"computed"
453+
} // another comment
454+
}
455+
}
456+
""",
457+
macros: ["function": MultilineFunctionMacro.self],
458+
indentationWidth: indentationWidth
459+
)
272460
}
273461

274462
func testAllLexicalContexts() {

0 commit comments

Comments
 (0)