-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add integration tests for Expand Macro actions (#1206)
* Add integration tests for Inline/Expand Macro actions - Validate the workflow of user calling Inline/Expand Macro actions on a swift project with macro - Add test fixture for swift macro - Verify inline macro by asserting on inlined value after calling the action - Verify expand macro by asserting expanded macro document contain the right macro Issue: #1205
- Loading branch information
1 parent
cecfaed
commit d51edfc
Showing
5 changed files
with
225 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// swift-tools-version:5.9 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
import CompilerPluginSupport | ||
|
||
let package = Package( | ||
name: "swift-macro", | ||
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)], | ||
products: [ | ||
// Products define the executables and libraries a package produces, making them visible to other packages. | ||
.library( | ||
name: "swift-macro", | ||
targets: ["swift-macro"] | ||
), | ||
.executable( | ||
name: "swift-macroClient", | ||
targets: ["swift-macroClient"] | ||
), | ||
], | ||
dependencies: [ | ||
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0-latest"), | ||
], | ||
targets: [ | ||
// Targets are the basic building blocks of a package, defining a module or a test suite. | ||
// Targets can depend on other targets in this package and products from dependencies. | ||
// Macro implementation that performs the source transformation of a macro. | ||
.macro( | ||
name: "swift-macroMacros", | ||
dependencies: [ | ||
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"), | ||
.product(name: "SwiftCompilerPlugin", package: "swift-syntax") | ||
] | ||
), | ||
|
||
// Library that exposes a macro as part of its API, which is used in client programs. | ||
.target(name: "swift-macro", dependencies: ["swift-macroMacros"]), | ||
|
||
// A client of the library, which is able to use the macro in its own code. | ||
.executableTarget(name: "swift-macroClient", dependencies: ["swift-macro"]), | ||
] | ||
) |
11 changes: 11 additions & 0 deletions
11
assets/test/swift-macro/Sources/swift-macro/swift_macro.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// The Swift Programming Language | ||
// https://docs.swift.org/swift-book | ||
|
||
/// A macro that produces both a value and a string containing the | ||
/// source code that generated the value. For example, | ||
/// | ||
/// #stringify(x + y) | ||
/// | ||
/// produces a tuple `(x + y, "x + y")`. | ||
@freestanding(expression) | ||
public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "swift_macroMacros", type: "StringifyMacro") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import swift_macro | ||
|
||
let a = 17 | ||
let b = 25 | ||
|
||
let (result, code) = #stringify(a + b) | ||
|
||
print("The value \(result) was produced by the code \"\(code)\"") |
33 changes: 33 additions & 0 deletions
33
assets/test/swift-macro/Sources/swift-macroMacros/swift_macroMacro.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import SwiftCompilerPlugin | ||
import SwiftSyntax | ||
import SwiftSyntaxBuilder | ||
import SwiftSyntaxMacros | ||
|
||
/// Implementation of the `stringify` macro, which takes an expression | ||
/// of any type and produces a tuple containing the value of that expression | ||
/// and the source code that produced the value. For example | ||
/// | ||
/// #stringify(x + y) | ||
/// | ||
/// will expand to | ||
/// | ||
/// (x + y, "x + y") | ||
public struct StringifyMacro: ExpressionMacro { | ||
public static func expansion( | ||
of node: some FreestandingMacroExpansionSyntax, | ||
in context: some MacroExpansionContext | ||
) -> ExprSyntax { | ||
guard let argument = node.arguments.first?.expression else { | ||
fatalError("compiler bug: the macro does not have any arguments") | ||
} | ||
|
||
return "(\(argument), \(literal: argument.description))" | ||
} | ||
} | ||
|
||
@main | ||
struct swift_macroPlugin: CompilerPlugin { | ||
let providingMacros: [Macro.Type] = [ | ||
StringifyMacro.self, | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the VS Code Swift open source project | ||
// | ||
// Copyright (c) 2024 the VS Code Swift project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import * as vscode from "vscode"; | ||
import * as langclient from "vscode-languageclient/node"; | ||
import { expect } from "chai"; | ||
import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClientManager"; | ||
import { WorkspaceContext } from "../../../src/WorkspaceContext"; | ||
import { testAssetUri } from "../../fixtures"; | ||
import { FolderContext } from "../../../src/FolderContext"; | ||
import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities"; | ||
import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; | ||
import { Version } from "../../../src/utilities/version"; | ||
import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; | ||
|
||
async function waitForClientState( | ||
languageClientManager: LanguageClientManager, | ||
expectedState: langclient.State | ||
): Promise<langclient.State> { | ||
let clientState = undefined; | ||
while (clientState !== expectedState) { | ||
clientState = await languageClientManager.useLanguageClient(async client => client.state); | ||
console.warn("Language client is not ready yet. Retrying in 100 ms..."); | ||
await new Promise(resolve => setTimeout(resolve, 100)); | ||
} | ||
return clientState; | ||
} | ||
|
||
suite("Integration, Macros Functionality Support with Sourcekit-lsp", function () { | ||
// Take around 60 seconds if running in isolation, longer than default timeout | ||
this.timeout(2 * 60 * 1000); | ||
|
||
let clientManager: LanguageClientManager; | ||
let workspaceContext: WorkspaceContext; | ||
let folderContext: FolderContext; | ||
|
||
activateExtensionForSuite({ | ||
async setup(ctx) { | ||
workspaceContext = ctx; | ||
// Expand Macro support in Swift started from 6.1 | ||
if (workspaceContext.swiftVersion.isLessThan(new Version(6, 1, 0))) { | ||
this.skip(); | ||
} | ||
|
||
// Wait for a clean starting point, and build all tasks for the fixture | ||
await waitForNoRunningTasks(); | ||
folderContext = await folderInRootWorkspace("swift-macro", workspaceContext); | ||
await workspaceContext.focusFolder(folderContext); | ||
const tasks = (await getBuildAllTask(folderContext)) as SwiftTask; | ||
const { exitCode, output } = await executeTaskAndWaitForResult(tasks); | ||
expect(exitCode, `${output}`).to.equal(0); | ||
|
||
// Ensure lsp client is ready | ||
clientManager = workspaceContext.languageClientManager; | ||
const clientState = await waitForClientState(clientManager, langclient.State.Running); | ||
expect(clientState).to.equals(langclient.State.Running); | ||
}, | ||
}); | ||
|
||
test("Expand Macro", async function () { | ||
// Focus on the file of interest | ||
const uri = testAssetUri("swift-macro/Sources/swift-macroClient/main.swift"); | ||
await vscode.window.showTextDocument(uri); | ||
|
||
// Beginning of macro, # | ||
const position = new vscode.Position(5, 21); | ||
|
||
// Create a range starting and ending at the specified position | ||
const range = new vscode.Range(position, position); | ||
|
||
// Execute the code action provider command | ||
const codeActions = await vscode.commands.executeCommand<vscode.CodeAction[]>( | ||
"vscode.executeCodeActionProvider", | ||
uri, | ||
range | ||
); | ||
|
||
const expectedMacro = '(a + b, "a + b")'; | ||
|
||
// Find the "expand.macro.command" action | ||
const expandMacroAction = codeActions.find( | ||
action => action.command?.command === "expand.macro.command" | ||
); | ||
|
||
// Assert that the expand macro command is available | ||
expect(expandMacroAction).is.not.undefined; | ||
|
||
// Set up a promise that resolves when the expected document is opened | ||
const expandedMacroUriPromise = new Promise<vscode.TextDocument>((resolve, reject) => { | ||
const disposable = vscode.workspace.onDidOpenTextDocument(openedDocument => { | ||
if (openedDocument.uri.scheme === "sourcekit-lsp") { | ||
disposable.dispose(); // Stop listening once we find the desired document | ||
resolve(openedDocument); | ||
} | ||
}); | ||
|
||
// Set a timeout to reject the promise if the document is not found | ||
setTimeout(() => { | ||
disposable.dispose(); | ||
reject(new Error("Timed out waiting for sourcekit-lsp document to be opened.")); | ||
}, 10000); // Wait up to 10 seconds for the document | ||
}); | ||
|
||
// Run expand macro action | ||
const command = expandMacroAction!.command!; | ||
expect(command.arguments).is.not.undefined; | ||
const commandArgs = command.arguments!; | ||
await vscode.commands.executeCommand(command.command, ...commandArgs); | ||
|
||
// Wait for the expanded macro document to be opened | ||
const referenceDocument = await expandedMacroUriPromise; | ||
|
||
// Verify that the reference document was successfully opened | ||
expect(referenceDocument).to.not.be.undefined; | ||
|
||
// Assert that the content contains the expected result | ||
const content = referenceDocument.getText(); | ||
expect(content).to.include(expectedMacro); | ||
}); | ||
}); |