Skip to content

Commit 057bdfb

Browse files
authored
Merge pull request #2105 from ahoppen/ahoppen/509/member-attribute-formatting
[509] Fix a formatting issue if a member attribute macro is applied to properties that already have an attribute
2 parents d45ffea + 73aad93 commit 057bdfb

File tree

2 files changed

+205
-6
lines changed

2 files changed

+205
-6
lines changed

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -573,14 +573,21 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
573573
// Expand member attribute members attached to the declaration context.
574574
// Note that MemberAttribute macros are _not_ applied to generated members
575575
if let parentDeclGroup, let decl = item.decl.asProtocol(WithAttributesSyntax.self) {
576-
var newAttributes = expandAttributesFromMemberAttributeMacros(
577-
of: item.decl,
578-
parentDecl: parentDeclGroup
576+
var newAttributes = AttributeListSyntax(
577+
expandAttributesFromMemberAttributeMacros(
578+
of: item.decl,
579+
parentDecl: parentDeclGroup
580+
)
581+
.map { visit($0) }
579582
)
580-
.map { visit($0) }
581583
if !newAttributes.isEmpty {
582-
newAttributes.insert(contentsOf: decl.attributes, at: 0)
583-
item.decl = decl.with(\.attributes, AttributeListSyntax(newAttributes)).cast(DeclSyntax.self)
584+
// Transfer the trailing trivia from the old attributes to the new attributes.
585+
// This way, we essentially insert the new attributes right after the last attribute in source
586+
// but before its trailing trivia, keeping the trivia that separates the attribute block
587+
// from the variable itself.
588+
newAttributes.trailingTrivia = newAttributes.trailingTrivia + decl.attributes.trailingTrivia
589+
newAttributes.insert(contentsOf: decl.attributes.with(\.trailingTrivia, []), at: newAttributes.startIndex)
590+
item.decl = decl.with(\.attributes, newAttributes).cast(DeclSyntax.self)
584591
}
585592
}
586593

Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,198 @@ final class MacroSystemTests: XCTestCase {
12281228
)
12291229
}
12301230

1231+
func testMemberAttributeMacroOnPropertyThatAlreadyHasAttribute() {
1232+
struct TestMacro: MemberAttributeMacro {
1233+
static func expansion(
1234+
of node: AttributeSyntax,
1235+
attachedTo decl: some DeclGroupSyntax,
1236+
providingAttributesFor member: some DeclSyntaxProtocol,
1237+
in context: some MacroExpansionContext
1238+
) throws -> [AttributeSyntax] {
1239+
return [
1240+
AttributeSyntax(
1241+
attributeName: IdentifierTypeSyntax(
1242+
name: .identifier("Wrapper")
1243+
)
1244+
)
1245+
]
1246+
}
1247+
}
1248+
1249+
assertMacroExpansion(
1250+
"""
1251+
@Test
1252+
struct Foo {
1253+
@available(*, deprecated) var x: Int
1254+
}
1255+
""",
1256+
expandedSource: """
1257+
struct Foo {
1258+
@available(*, deprecated)
1259+
@Wrapper var x: Int
1260+
}
1261+
""",
1262+
macros: ["Test": TestMacro.self],
1263+
indentationWidth: indentationWidth
1264+
)
1265+
1266+
assertMacroExpansion(
1267+
"""
1268+
@Test
1269+
struct Foo {
1270+
@available(*, deprecated) /* x */ var x: Int
1271+
}
1272+
""",
1273+
expandedSource: """
1274+
struct Foo {
1275+
@available(*, deprecated)
1276+
@Wrapper /* x */ var x: Int
1277+
}
1278+
""",
1279+
macros: ["Test": TestMacro.self],
1280+
indentationWidth: indentationWidth
1281+
)
1282+
1283+
assertMacroExpansion(
1284+
"""
1285+
@Test
1286+
struct Foo {
1287+
@available(*, deprecated)
1288+
1289+
var x: Int
1290+
}
1291+
""",
1292+
expandedSource: """
1293+
struct Foo {
1294+
@available(*, deprecated)
1295+
@Wrapper
1296+
1297+
var x: Int
1298+
}
1299+
""",
1300+
macros: ["Test": TestMacro.self],
1301+
indentationWidth: indentationWidth
1302+
)
1303+
1304+
assertMacroExpansion(
1305+
"""
1306+
@Test
1307+
struct Foo {
1308+
@available(*, deprecated) // some comment
1309+
1310+
var x: Int
1311+
}
1312+
""",
1313+
expandedSource: """
1314+
struct Foo {
1315+
@available(*, deprecated)
1316+
@Wrapper // some comment
1317+
1318+
var x: Int
1319+
}
1320+
""",
1321+
macros: ["Test": TestMacro.self],
1322+
indentationWidth: indentationWidth
1323+
)
1324+
}
1325+
1326+
func testMemberAttributeWithTriviaMacroOnPropertyThatAlreadyHasAttribute() {
1327+
struct TestMacro: MemberAttributeMacro {
1328+
static func expansion(
1329+
of node: AttributeSyntax,
1330+
attachedTo decl: some DeclGroupSyntax,
1331+
providingAttributesFor member: some DeclSyntaxProtocol,
1332+
in context: some MacroExpansionContext
1333+
) throws -> [AttributeSyntax] {
1334+
return [
1335+
AttributeSyntax(
1336+
leadingTrivia: .blockComment("/* start */"),
1337+
attributeName: IdentifierTypeSyntax(
1338+
name: .identifier("Wrapper")
1339+
),
1340+
trailingTrivia: .blockComment("/* end */")
1341+
)
1342+
]
1343+
}
1344+
}
1345+
1346+
assertMacroExpansion(
1347+
"""
1348+
@Test
1349+
struct Foo {
1350+
@available(*, deprecated) var x: Int
1351+
}
1352+
""",
1353+
expandedSource: """
1354+
struct Foo {
1355+
@available(*, deprecated)
1356+
/* start */@Wrapper/* end */ var x: Int
1357+
}
1358+
""",
1359+
macros: ["Test": TestMacro.self],
1360+
indentationWidth: indentationWidth
1361+
)
1362+
1363+
assertMacroExpansion(
1364+
"""
1365+
@Test
1366+
struct Foo {
1367+
@available(*, deprecated) /* x */ var x: Int
1368+
}
1369+
""",
1370+
expandedSource: """
1371+
struct Foo {
1372+
@available(*, deprecated)
1373+
/* start */@Wrapper/* end */ /* x */ var x: Int
1374+
}
1375+
""",
1376+
macros: ["Test": TestMacro.self],
1377+
indentationWidth: indentationWidth
1378+
)
1379+
1380+
assertMacroExpansion(
1381+
"""
1382+
@Test
1383+
struct Foo {
1384+
@available(*, deprecated)
1385+
1386+
var x: Int
1387+
}
1388+
""",
1389+
expandedSource: """
1390+
struct Foo {
1391+
@available(*, deprecated)
1392+
/* start */@Wrapper/* end */
1393+
1394+
var x: Int
1395+
}
1396+
""",
1397+
macros: ["Test": TestMacro.self],
1398+
indentationWidth: indentationWidth
1399+
)
1400+
1401+
assertMacroExpansion(
1402+
"""
1403+
@Test
1404+
struct Foo {
1405+
@available(*, deprecated) // some comment
1406+
1407+
var x: Int
1408+
}
1409+
""",
1410+
expandedSource: """
1411+
struct Foo {
1412+
@available(*, deprecated)
1413+
/* start */@Wrapper/* end */ // some comment
1414+
1415+
var x: Int
1416+
}
1417+
""",
1418+
macros: ["Test": TestMacro.self],
1419+
indentationWidth: indentationWidth
1420+
)
1421+
}
1422+
12311423
func testTypeWrapperTransform() {
12321424
assertMacroExpansion(
12331425
"""

0 commit comments

Comments
 (0)