Skip to main content

no-unused-handle-result

Generated page

This page is generated from packages/eslint-plugin-reforged/docs/no-unused-handle-result.md by docs:collect. Edit the source, not this page.

Reports an expression statement that discards a creation (Unit.create(...), CreateTimer(), AddSpecialEffect(...)) or a Filter(...)/Condition(...) boolexpr. An error in the recommended config; keep the reference and destroy the object when it is no longer needed.

Why​

Pitfalls L1 and D8 of the catalogue (#15). Lua's collector frees Lua values, not engine objects: "you don't need to actively remove Lua objects. In fact you can't, because there are no destroy-methods for any type of data in native Lua" (A comprehensive guide to mapping in Lua, "The Garbage Collector"). Engine objects need their destroy Native: boolexpr, conditionfunc and filterfunc "must be explicitly destroyed with DestroyBoolExpr/DestroyCondition/DestroyFilter to prevent leaks", and locations "must be removed with RemoveLocation" (jassdoc). A dropped result can never be destroyed, so it leaks for the rest of the game; "Huge amount of memory leaks" is itself listed as a desync cause (Known causes of desync).

A creation is a Wrapper static whose name starts with create, or a Native listed in the plugin's data/creation-natives.json that returns a Handle type (see no-handles-at-module-top-level). Timer.after and the other statics not named create* own what they make and are never reported, nor are lookups (GetTriggerUnit()) and the registration Natives (TriggerRegisterTimerEvent, TriggerAddAction).

Incorrect​

import { Init, MapPlayer, Unit } from "reforged-ts";

Init.onGlobals(() => {
AddSpecialEffect(
"Abilities\\Spells\\Human\\ThunderClap\\ThunderClapCaster.mdl",
0,
0,
); // leaks the effect
Filter(() => true); // leaks the filterfunc
Unit.create(MapPlayer.fromIndex(0)!, FourCC("hfoo"), 0, 0); // nothing refers to the unit
});

Correct​

import { Effect, Init, MapPlayer, Timer, Unit } from "reforged-ts";

Init.onGlobals(() => {
const effect = Effect.create(
"Abilities\\Spells\\Human\\ThunderClap\\ThunderClapCaster.mdl",
0,
0,
);
effect.destroy(); // plays its death animation, then frees it
const footman = Unit.create(MapPlayer.fromIndex(0)!, FourCC("hfoo"), 0, 0);
Timer.after(30, () => footman.kill()); // Timer.after owns its timer
});

Options​

None.

Suggestions and fixes​

None: adding a destroy() would change what the code does, and the right place to destroy is the author's call.

When not to use it​

When the object is reached again another way, such as a unit found through its group or a region, silence that one line and say why. To discard on purpose without an escape, write void CreateUnit(...): the rule does not report a void expression.

// eslint-disable-next-line reforged/no-unused-handle-result -- the unit is found again through its group
CreateUnit(Player(0)!, FourCC("hfoo"), 0, 0, 0);