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