Skip to content

Commit 4e9bcf6

Browse files
committed
Add LSP support for showing @attached Macro Expansions
----- - Change Line & Column number to One-based Indexing - Add test case for `@attached` macro expansions - Fix Bug where `ShowDocumentRequest` fails due to `selection` -----
1 parent e75fac5 commit 4e9bcf6

File tree

2 files changed

+206
-3
lines changed

2 files changed

+206
-3
lines changed

Sources/SourceKitLSP/Swift/MacroExpansion.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ extension SwiftLanguageService {
9595

9696
// github permalink notation for position range
9797
let macroExpansionPositionRangeIndicator =
98-
"L\(macroEdit.range.lowerBound.line)C\(macroEdit.range.lowerBound.utf16index)-L\(macroEdit.range.upperBound.line)C\(macroEdit.range.upperBound.utf16index)"
98+
"L\(macroEdit.range.lowerBound.line + 1)C\(macroEdit.range.lowerBound.utf16index + 1)-L\(macroEdit.range.upperBound.line + 1)C\(macroEdit.range.upperBound.utf16index + 1)"
9999

100100
let macroExpansionFilePath =
101101
macroExpansionBufferDirectoryURL
@@ -112,7 +112,7 @@ extension SwiftLanguageService {
112112
}
113113

114114
Task {
115-
let req = ShowDocumentRequest(uri: DocumentURI(macroExpansionFilePath), selection: macroEdit.range)
115+
let req = ShowDocumentRequest(uri: DocumentURI(macroExpansionFilePath))
116116

117117
let response = await orLog("Sending ShowDocumentRequest to Client") {
118118
try await sourceKitLSPServer.sendRequestToClient(req)

Tests/SourceKitLSPTests/ExecuteCommandTests.swift

Lines changed: 204 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,210 @@ final class ExecuteCommandTests: XCTestCase {
252252

253253
XCTAssertEqual(
254254
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(),
256459
"Failed for position range between \(positionMarker.start) and \(positionMarker.end)"
257460
)
258461
}

0 commit comments

Comments
 (0)