@@ -252,7 +252,209 @@ final class ExecuteCommandTests: XCTestCase {
252
252
253
253
XCTAssertEqual (
254
254
url. lastPathComponent,
255
- " MyMacroClient_L4C2-L4C19.swift " ,
255
+ " MyMacroClient_L5C3-L5C20.swift " ,
256
+ " Failed for position range between \( positionMarker. start) and \( positionMarker. end) "
257
+ )
258
+ }
259
+ }
260
+
261
+ func testAttachedMacroExpansion( ) async throws {
262
+ try await SkipUnless . canBuildMacroUsingSwiftSyntaxFromSourceKitLSPBuild ( )
263
+
264
+ let options = SourceKitLSPOptions . testDefault ( experimentalFeatures: [ . showMacroExpansions] )
265
+
266
+ let project = try await SwiftPMTestProject (
267
+ files: [
268
+ " MyMacros/MyMacros.swift " : #"""
269
+ import SwiftCompilerPlugin
270
+ import SwiftSyntax
271
+ import SwiftSyntaxBuilder
272
+ import SwiftSyntaxMacros
273
+
274
+ public struct DictionaryStorageMacro {}
275
+
276
+ extension DictionaryStorageMacro: MemberMacro {
277
+ public static func expansion(
278
+ of node: AttributeSyntax,
279
+ providingMembersOf declaration: some DeclGroupSyntax,
280
+ in context: some MacroExpansionContext
281
+ ) throws -> [DeclSyntax] {
282
+ return ["\n var _storage: [String: Any] = [:]"]
283
+ }
284
+ }
285
+
286
+ extension DictionaryStorageMacro: MemberAttributeMacro {
287
+ public static func expansion(
288
+ of node: AttributeSyntax,
289
+ attachedTo declaration: some DeclGroupSyntax,
290
+ providingAttributesFor member: some DeclSyntaxProtocol,
291
+ in context: some MacroExpansionContext
292
+ ) throws -> [AttributeSyntax] {
293
+ return [
294
+ AttributeSyntax(
295
+ leadingTrivia: [.newlines(1), .spaces(2)],
296
+ attributeName: IdentifierTypeSyntax(
297
+ name: .identifier("DictionaryStorageProperty")
298
+ )
299
+ )
300
+ ]
301
+ }
302
+ }
303
+
304
+ public struct DictionaryStoragePropertyMacro: AccessorMacro {
305
+ public static func expansion<
306
+ Context: MacroExpansionContext,
307
+ Declaration: DeclSyntaxProtocol
308
+ >(
309
+ of node: AttributeSyntax,
310
+ providingAccessorsOf declaration: Declaration,
311
+ in context: Context
312
+ ) throws -> [AccessorDeclSyntax] {
313
+ guard let binding = declaration.as(VariableDeclSyntax.self)?.bindings.first,
314
+ let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
315
+ binding.accessorBlock == nil,
316
+ let type = binding.typeAnnotation?.type,
317
+ let defaultValue = binding.initializer?.value,
318
+ identifier.text != "_storage"
319
+ else {
320
+ return []
321
+ }
322
+
323
+ return [
324
+ """
325
+ get {
326
+ _storage[\(literal: identifier.text), default: \(defaultValue)] as! \(type)
327
+ }
328
+ """,
329
+ """
330
+ set {
331
+ _storage[\(literal: identifier.text)] = newValue
332
+ }
333
+ """,
334
+ ]
335
+ }
336
+ }
337
+
338
+ @main
339
+ struct MyMacroPlugin: CompilerPlugin {
340
+ let providingMacros: [Macro.Type] = [
341
+ DictionaryStorageMacro.self,
342
+ DictionaryStoragePropertyMacro.self
343
+ ]
344
+ }
345
+ """# ,
346
+ " MyMacroClient/MyMacroClient.swift " : #"""
347
+ @attached(memberAttribute)
348
+ @attached(member, names: named(_storage))
349
+ public macro DictionaryStorage() = #externalMacro(module: "MyMacros", type: "DictionaryStorageMacro")
350
+
351
+ @attached(accessor)
352
+ public macro DictionaryStorageProperty() =
353
+ #externalMacro(module: "MyMacros", type: "DictionaryStoragePropertyMacro")
354
+
355
+ 1️⃣@2️⃣DictionaryStorage3️⃣
356
+ struct Point {
357
+ var x: Int = 1
358
+ var y: Int = 2
359
+ }
360
+ """# ,
361
+ ] ,
362
+ manifest: SwiftPMTestProject . macroPackageManifest,
363
+ options: options
364
+ )
365
+ try await SwiftPMTestProject . build ( at: project. scratchDirectory)
366
+
367
+ let ( uri, positions) = try project. openDocument ( " MyMacroClient.swift " )
368
+
369
+ let positionMarkersToBeTested = [
370
+ ( start: " 1️⃣ " , end: " 1️⃣ " ) ,
371
+ ( start: " 2️⃣ " , end: " 2️⃣ " ) ,
372
+ ( start: " 1️⃣ " , end: " 3️⃣ " ) ,
373
+ ( start: " 2️⃣ " , end: " 3️⃣ " ) ,
374
+ ]
375
+
376
+ for positionMarker in positionMarkersToBeTested {
377
+ let args = ExpandMacroCommand (
378
+ positionRange: positions [ positionMarker. start] ..< positions [ positionMarker. end] ,
379
+ textDocument: TextDocumentIdentifier ( uri)
380
+ )
381
+
382
+ let metadata = SourceKitLSPCommandMetadata ( textDocument: TextDocumentIdentifier ( uri) )
383
+
384
+ var command = args. asCommand ( )
385
+ command. arguments? . append ( metadata. encodeToLSPAny ( ) )
386
+
387
+ let request = ExecuteCommandRequest ( command: command. command, arguments: command. arguments)
388
+
389
+ let expectation = self . expectation ( description: " Handle Show Document Requests " )
390
+ expectation. expectedFulfillmentCount = 3
391
+
392
+ let showDocumentRequestURIs = ThreadSafeBox < [ DocumentURI ? ] > ( initialValue: [ nil , nil , nil ] )
393
+
394
+ for i in 0 ... 2 {
395
+ project. testClient. handleSingleRequest { ( req: ShowDocumentRequest ) in
396
+ showDocumentRequestURIs. value [ i] = req. uri
397
+ expectation. fulfill ( )
398
+ return ShowDocumentResponse ( success: true )
399
+ }
400
+ }
401
+
402
+ let result = try await project. testClient. send ( request)
403
+
404
+ guard let resultArray: [ RefactoringEdit ] = Array ( fromLSPArray: result ?? . null) else {
405
+ XCTFail (
406
+ " Result is not an array. Failed for position range between \( positionMarker. start) and \( positionMarker. end) "
407
+ )
408
+ return
409
+ }
410
+
411
+ XCTAssertEqual (
412
+ resultArray. count,
413
+ 4 ,
414
+ " resultArray count is not equal to four. Failed for position range between \( positionMarker. start) and \( positionMarker. end) "
415
+ )
416
+
417
+ XCTAssertEqual (
418
+ resultArray. map { $0. newText } . sorted ( ) ,
419
+ [
420
+ " " ,
421
+ " @DictionaryStorageProperty " ,
422
+ " @DictionaryStorageProperty " ,
423
+ " var _storage: [String: Any] = [:] " ,
424
+ ] . sorted ( ) ,
425
+ " Wrong macro expansion. Failed for position range between \( positionMarker. start) and \( positionMarker. end) "
426
+ )
427
+
428
+ try await fulfillmentOfOrThrow ( [ expectation] )
429
+
430
+ let urls = try showDocumentRequestURIs. value. map {
431
+ try XCTUnwrap (
432
+ $0? . fileURL,
433
+ " Failed for position range between \( positionMarker. start) and \( positionMarker. end) "
434
+ )
435
+ }
436
+
437
+ let filesContents = try urls. map {
438
+ try String ( contentsOf: $0, encoding: . utf8)
439
+ }
440
+
441
+ XCTAssertEqual (
442
+ filesContents. sorted ( ) ,
443
+ [
444
+ " @DictionaryStorageProperty " ,
445
+ " @DictionaryStorageProperty " ,
446
+ " var _storage: [String: Any] = [:] " ,
447
+ ] . sorted ( ) ,
448
+ " Files doesn't contain correct macro expansion. Failed for position range between \( positionMarker. start) and \( positionMarker. end) "
449
+ )
450
+
451
+ XCTAssertEqual (
452
+ urls. map { $0. lastPathComponent } . sorted ( ) ,
453
+ [
454
+ " MyMacroClient_L11C3-L11C3.swift " ,
455
+ " MyMacroClient_L12C3-L12C3.swift " ,
456
+ " MyMacroClient_L13C1-L13C1.swift " ,
457
+ ] . sorted ( ) ,
256
458
" Failed for position range between \( positionMarker. start) and \( positionMarker. end) "
257
459
)
258
460
}
0 commit comments