Repository: League-of-Foundry-Developers/foundry-vtt-types (ts)

Issue:
Update macro.execute to accept extended scope
The `scope` argument should be fully extensible to take any number of additional arguments to pass through

Pull request:
Macro execute fixes
Closes #2658 while also working in bug fixes for the return value

Patch:
diff --git a/src/foundry/client/data/documents/macro.d.mts b/src/foundry/client/data/documents/macro.d.mts
index e6e593e56..d797e6c61 100644
--- a/src/foundry/client/data/documents/macro.d.mts
+++ b/src/foundry/client/data/documents/macro.d.mts
@@ -33,11 +33,10 @@ declare global {
     /**
      * Execute the Macro command.
      * @param scope - Macro execution scope which is passed to script macros
-     * @returns A created ChatMessage from chat macros or returned value from script macros
+     * @remarks `type === "chat"` returns void, `type === "script"` returns the promise
+     * @privateRemarks Foundry's stated return value is incorrect, #executeChat only returns `void`
      */
-
-    // TODO: Test if additional scope can be passed
-    execute(scope?: Scope): this["type"] extends "chat" ? ChatMessage.ConfiguredInstance : any;
+    execute(scope?: Scope): void | Promise<unknown>;
 
     _onClickDocumentLink(event: MouseEvent): ReturnType<this["execute"]>;
   }
@@ -53,4 +52,9 @@ interface Scope {
    * A Token which is the protagonist of the executed action
    */
   token?: Token;
+
+  /**
+   * @remarks Additional arguments passed as part of the scope
+   */
+  [arg: string]: unknown;
 }


Test patch:
diff --git a/tests/foundry/client/data/documents/macro.test-d.ts b/tests/foundry/client/data/documents/macro.test-d.ts
index 4749f7ab5..e206a814f 100644
--- a/tests/foundry/client/data/documents/macro.test-d.ts
+++ b/tests/foundry/client/data/documents/macro.test-d.ts
@@ -1,24 +1,43 @@
 import { expectTypeOf } from "vitest";
 import type { StoredDocument } from "../../../../../src/types/utils.d.mts";
 
-const macro = new Macro({ name: "my macro", scope: "global", type: "script" });
+const myMacro = new Macro({ name: "my macro", scope: "global", type: "script" });
 
 // properties and functions added by the concrete `Macro` class
-expectTypeOf(macro.isAuthor).toEqualTypeOf<boolean>();
-expectTypeOf(macro.execute).toEqualTypeOf<(scope?: { actor?: Actor; token?: Token }) => any>();
+expectTypeOf(myMacro.execute()).toEqualTypeOf<void | Promise<unknown>>();
+if (myMacro.type === "script") {
+  expectTypeOf(myMacro.type).toEqualTypeOf<"script">();
+  expectTypeOf(myMacro.isAuthor).toEqualTypeOf<boolean>();
+  expectTypeOf(await myMacro.execute({ effect: new ActiveEffect() })).toEqualTypeOf<unknown>();
+  expectTypeOf(
+    await myMacro.execute({ effect: new ActiveEffect(), actor: new Actor({ name: "foo", type: "base" }) }),
+  ).toEqualTypeOf<unknown>();
+
+  // @ts-expect-error The actor property must be an actual Actor
+  expectTypeOf(await myMacro.execute({ actor: new ActiveEffect() })).toEqualTypeOf<unknown>();
+  expectTypeOf(
+    // @ts-expect-error The actor property must be an actual Actor
+    await myMacro.execute({ actor: new ActiveEffect(), effect: new ActiveEffect() }),
+  ).toEqualTypeOf<unknown>();
+}
+if (myMacro.type === "chat") {
+  expectTypeOf(myMacro.type).toEqualTypeOf<"chat">();
+  // Unable to successfully narrow the type here, *should* be void
+  expectTypeOf(myMacro.execute()).toEqualTypeOf<void | Promise<unknown>>();
+}
 
 // properties and functions of `ClientDocumentMixin`
-expectTypeOf(macro.apps).toEqualTypeOf<Record<string, Application>>();
-expectTypeOf(macro.collection).toEqualTypeOf<Collection<Macro>>();
-expectTypeOf(macro.folder).toEqualTypeOf<Folder | null>();
-expectTypeOf(macro.isOwner).toEqualTypeOf<boolean>();
+expectTypeOf(myMacro.apps).toEqualTypeOf<Record<string, Application>>();
+expectTypeOf(myMacro.collection).toEqualTypeOf<Collection<Macro>>();
+expectTypeOf(myMacro.folder).toEqualTypeOf<Folder | null>();
+expectTypeOf(myMacro.isOwner).toEqualTypeOf<boolean>();
 
 // static properties and functions of `ClientDocumentMixin`
 expectTypeOf(Macro.createDialog()).toEqualTypeOf<Promise<Macro | null | undefined>>();
 
 // properties of `Document`
-expectTypeOf(macro.parent).toEqualTypeOf<null>();
-expectTypeOf(macro.pack).toEqualTypeOf<string | null>();
+expectTypeOf(myMacro.parent).toEqualTypeOf<null>();
+expectTypeOf(myMacro.pack).toEqualTypeOf<string | null>();
 
 // static properties of `Document`
 expectTypeOf(Macro.create({ name: "Some Macro" })).toEqualTypeOf<Promise<StoredDocument<Macro> | undefined>>();
diff --git a/tests/foundry/common/documents/macro.test-d.ts b/tests/foundry/common/documents/macro.test-d.ts
index 0a7feaf5a..013657d99 100644
--- a/tests/foundry/common/documents/macro.test-d.ts
+++ b/tests/foundry/common/documents/macro.test-d.ts
@@ -7,3 +7,7 @@ new foundry.documents.BaseMacro();
 new foundry.documents.BaseMacro({});
 
 expectTypeOf(new foundry.documents.BaseMacro({ name: "foo" })).toEqualTypeOf<foundry.documents.BaseMacro>();
+
+const myMacro = new foundry.documents.BaseMacro({ name: "foo" });
+
+expectTypeOf(myMacro.type).toEqualTypeOf<"chat" | "script">();