Lints - SQF
required_version
Code: L-S01
Default Severity: Error
Minimum Severity: Error
Checks for command usage that requires a newer version than specified in CfgPatches
Example
Incorrect
class CfgPatches {
class MyAddon {
units[] = {};
weapons[] = {};
requiredVersion = 2.00;
};
};
private _leaky = getWaterLeakiness vehicle player; // getWaterLeakiness requires 2.16
Check the wiki to see what in version commands were introduced.
event_insufficient_version
Code: L-S02IV
Default Severity: Error
Minimum Severity: Error
Checks for event handlers that require a newer version than specified in CfgPatches
Example
Incorrect
class CfgPatches {
class MyAddon {
units[] = {};
weapons[] = {};
requiredVersion = 2.00;
};
};
_this addEventHandler ["OpticsModeChanged", { // Requires 2.10
hint 'Optics mode changed';
}];
Check the wiki to see what in version events were introduced.
event_unknown
Code: L-S02UE
Default Severity: Warning
Minimum Severity: Help
Checks for unknown event used in event handlers
Configuration
- ignore: List of unknown event names to ignore
[lints.sqf.event_unknown]
options.ignore = [
"HealingReceived",
]
Example
Incorrect
_this addEventHandler ["HealingReceived", { // HealingReceived is not a valid event
hint 'Healing received';
}];
Check the wiki to see what events are available.
event_incorrect_command
Code: L-S02IC
Default Severity: Error
Minimum Severity: Error
Checks for event handlers used with incorrect commands
Example
Incorrect
_this addEventHandler ["MPHit", {
hint 'Hit';
}];
Correct
_this addMPEventHandler ["MPHit", {
hint 'Hit';
}];
static_typename
Code: L-S03
Default Severity: Warning
Minimum Severity: Help
Checks for typeName on static values, which can be replaced with the string type directly
Example
Incorrect
if (typeName _myVar == typeName "") then {
hint "String";
};
Correct
if (typeName _myVar == "STRING") then {
hint "String";
};
Explanation
typeName is a command that returns the type of a variable. When used on a constant value, it is slower than using the type directly.
command_case
Code: L-S04
Default Severity: Help
Minimum Severity: Help
Checks command usage for casing that matches the wiki
Configuration
- ignore: An array of commands to ignore
[lints.sqf.command_case]
options.ignore = [
"ASLtoAGL",
"AGLtoASL",
]
Example
Incorrect
private _leaky = getwaterleakiness vehicle player;
Correct
private _leaky = getWaterLeakiness vehicle player;
if_assign
Code: L-S05
Default Severity: Warning
Minimum Severity: Help
Checks if statements that are used as assignments when select or parseNumber would be more appropriate
Example
Incorrect
private _x = if (_myVar) then {1} else {0};
Correct
private _x = parseNumber _myVar;
Incorrect
private _x = if (_myVar) then {"apple"} else {"orange"};
Correct
private _x = ["orange", "apple"] select _myVar;
Explanation
if statements that are used as assignments and only return a static value can be replaced with the faster select or parseNumber commands.
find_in_str
Code: L-S06
Default Severity: Warning
Minimum Severity: Help
Checks for find commands that can be replaced with in
Example
Incorrect
if (_haystack find _needle > -1) ...
Correct
if (_needle in _haystack) ...
Explanation
The in command is faster than find when searching for a substring in a string.
select_parse_number
Code: L-S07
Default Severity: Warning
Minimum Severity: Help
Checks for select commands that can be replaced with parseNumber
Example
Incorrect
private _isWater = [0, 1] select (surfaceIsWater getPos player);
Correct
private _isWater = parseNumber (surfaceIsWater getPos player);
Incorrect
private _isLand = [1, 0] select (surfaceIsWater getPos player);
Correct
private _isLand = parseNumber !(surfaceIsWater getPos player);
Explanation
Using select on an array with 0 and 1 can be replaced with parseNumber for better performance.
format_args
Code: L-S08
Default Severity: Error
Minimum Severity: Warning
Checks for format commands with incorrect argument counts
Example
Incorrect
private _text = format ["%1", "Hello", "World"];
Correct
private _text = format ["%1", "Hello World"];
Incorrect
private _text = format ["%1 %2", "Hello World"];
Correct
private _text = format ["%1 %2", "Hello", "World"];
Explanation
The format and formatText commands requires the correct number of arguments to match the format string.
banned_commands
Code: L-S09
Default Severity: Error
Minimum Severity: Warning
Checks for broken or banned commands.
Configuration
- banned: Additional commands to check for
- ignore: An array of commands to ignore
[lints.sqf.banned_commands]
options.banned = [
"execVM",
]
options.ignore = [
"echo",
]
Example
Incorrect
echo "Hello World"; // Doesn't exist in the retail game
Explanation
Checks for usage of broken or banned commands.
if_not_else
Code: L-S11
Default Severity: Help (Pedantic)
Minimum Severity: Help
Checks for unneeded not
Example
Incorrect
if (!alive player) then { player } else { objNull };
Correct
if (alive player) then { objNull } else { player };
! can be removed and else order swapped
invalid_args
Code: L-S12
Default Severity: Warning
Minimum Severity: Help
Invalid Args
Example
Incorrect
(vehicle player) setFuel true; // bad args: takes number 0-1
Explanation
Checks correct syntax usage.
Ignoring False Positives
If a variable usage is complicated and causing a false positive, you can add a #pragma to ignore the warning.
if (something) then { CouldBeNumberOrString = 5;}; // Lint will assume the variable is a number
if (otherthing) then { y = CouldBeNumberOrString + "c";}; // Throws: Invalid arguments to command `[B:+]`
// But the var could actually be set to a string from a different scope, so ignore this specific warning with:
#pragma hemtt ignore_variables ["CouldBeNumberOrString"] // Lint will now assume the variable could be anything
unused
Code: L-S12
Default Severity: Help (Pedantic)
Minimum Severity: Help
Unused Var
Configuration
- check_params: Checks for unused variables in
paramsarrays. Default: false
[lints.sqf.unused]
enabled = true
options.check_params = true
Example
Incorrect
private _z = 5; // and never used
Explanation
Checks for variables that are never used.
Ignoring False Positives
You can add a #pragma to ignore the warning for specific variables.
#pragma hemtt ignore_variables ["_z"]
private _z = 5; // and never used
undefined
Code: L-S13
Default Severity: Help
Minimum Severity: Help
Undefined Variable
Configuration
- check_orphan_code: Checks for undefined variables in orphan code blocks (code that does not seem to be directly called). This may lead to false positives. Default: false
[lints.sqf.undefined]
enabled = true
options.check_orphan_code = true
Example
Incorrect
systemChat _neverDefined;
Explanation
Checks that variables are defined.
Ignoring False Positives
If a variable is coming from a higher scope, you can add a #pragma to ignore the warning for specific variables.
#pragma hemtt ignore_variables ["_fromUpper"]
_fromUpper pushBack ["newItem"];
shadowed
Code: L-S15
Default Severity: Help (Pedantic)
Minimum Severity: Help
Shadowed Var
Example
Incorrect
private _z = 5;
private _z = 5;
Explanation
Checks for variables being shadowed.
not_private
Code: L-S16
Default Severity: Help (Pedantic)
Minimum Severity: Help
Not Private Var
Example
Incorrect
_z = 6;
Explanation
Checks local variables that are not private.
Ignoring False Positives
If a variable is coming from a higher scope and cannot be private, you can add a #pragma to ignore the warning for specific variables.
#pragma hemtt ignore_variables ["_fromUpper"]
_fromUpper = 5;
var_all_caps
Code: L-S17
Default Severity: Warning
Minimum Severity: Help
Checks for global variables that are ALL_CAPS and may actually be a undefined macro
Configuration
- ignore: An array of variables to ignore
[lints.sqf.var_all_caps]
options.ignore = [
"XMOD_TEST", "MYMOD_*",
]
Example
Incorrect
private _z = _y + DO_NOT_EXIST;
Explanation
Variables that are all caps are usually reserved for macros. This should help prevent any accidental typos or uses before definitions when using macros.
in_vehicle_check
Code: L-S18
Default Severity: Warning
Minimum Severity: Help
Recommends using isNull objectParent X instead of vehicle X == X
Example
Incorrect
if (vehicle player == player) then { ... };
Correct
if (isNull objectParent player) then { ... };
Explanation
Using isNull objectParent x is faster and more reliable than vehicle x == x for checking if a unit is currently in a vehicle.
extra_not
Code: L-S19
Default Severity: Help
Minimum Severity: Help
Checks for extra not before a comparison
Example
Incorrect
! (5 isEqualTo 6)
Correct
(5 isNotEqualTo 6)
bool_static_comparison
Code: L-S20
Default Severity: Warning
Minimum Severity: Help
Checks for a variable being compared to true or false
Example
Incorrect
if (_x == true) then {};
if (_y == false) then {};
Correct
if (_x) then {};
if (!_y) then {};
invalid_comparisons
Code: L-S21
Default Severity: Error
Minimum Severity: Warning
Checks for if statements with impossible or overlapping conditions
Example
Incorrect
// This will never be true
if (_x < 20 && _x > 30) then { ... };
// If _x is less than 20, it will also be less than 10
if (_x < 20 && _x < 10) then { ... };
Explanation
This lint checks for if statements with impossible or overlapping conditions. This can be caused by typos or incorrect logic. HEMTT is not able to determine the intent of the code, so it is up to the developer to fix the condition.
this_call
Code: L-S22
Default Severity: Warning (Disabled)
Minimum Severity: Help
Checks for usage of _this call, where _this is not necessary
Example
Incorrect
_this call _my_function;
Correct
call _my_function;
Explanation
When using call, the called code will inherit _this from the calling scope. This means that _this is not necessary in the call, and can be omitted for better performance.
reasign_reserved_variable
Code: L-S23
Default Severity: Error
Minimum Severity: Warning
Prevents reassigning reserved variables
Example
Incorrect
call {
_this = 1;
};
{
private _forEachIndex = random 5;
} forEach allUnits;
Explanation
Reassigning reserved variables can lead to unintentional behavior.
marker_update_spam
Code: L-S24
Default Severity: Warning
Minimum Severity: Help
Checks for repeated calls to global marker updates
Example
Incorrect
"my_marker" setMarkerAlpha 0.5;
"my_marker" setMarkerDir 90;
"my_marker" setMarkerSize [100, 200];
"my_marker" setMarkerShape "RECTANGLE";
Correct
"my_marker" setMarkerAlphaLocal 0.5;
"my_marker" setMarkerDirLocal 90;
"my_marker" setMarkerSizeLocal [100, 200];
"my_marker" setMarkerShape "RECTANGLE";
Explanation
The setMarker* commands send the entire state of the marker to all clients. This can be very expensive if done repeatedly.
Using the setMarker*Local on all calls except the last one will reduce the amount of data sent over the network.
count_array_comp
Code: L-S25
Default Severity: Help
Minimum Severity: Help
Count Array Comparison
Example
Incorrect
count _myArray == 0
Correct
_myArray isEqualTo []
Explanation
Checks for unoptimized array count checks.
short_circuit_bool_var
Code: L-S26
Default Severity: Help (Pedantic)
Minimum Severity: Help
Checks for inefficent short ciruit evaulation
Example
Incorrect
if (_test1 && {_test2}) then { };
if (_test1 && {_a isEqualTo _b}) then { };
Correct
if (_test1 && _test2) then { };
if (_test1 && _a isEqualTo _b) then { };
Explanation
Short circuit evaluation is not free: the right hand side is a code block that has to be created and called. When the right hand side is just a boolean variable, or a comparison between simple values, evaluating it eagerly is cheaper than the short circuit that skips it.
Comparisons are only reported when both sides are simple values (a variable, number, string or boolean). A comparison that calls a command is left alone, because the short circuit is often guarding it:
if (count _array > 0 && {_array select 0 isEqualTo "x"}) then { }; // not reported
False positives are possible if a variable could be undefined, e.g.:
someLogic = !isNil "z";
someLogic && {z}
select_count
Code: L-S27
Default Severity: Help
Minimum Severity: Help
Checks for _array select (count _array - 1) and suggests _array select -1
Example
Incorrect
_array select (count _array - 1);
Correct
_array select -1;
Explanation
select can take a negative index to select from the end of the array. This is more efficient than calculating the index from SQF.
banned_macros
Code: L-S28
Default Severity: Error
Minimum Severity: Warning
Checks for banned macro in release builds.
Configuration
- always: macros that are always banned
- release: macros that are banned on release builds
[lints.sqf.banned_macros]
options.always = [
"FREEZE_GAME",
]
options.release = [
"DEBUG_MODE_FULL",
]
Explanation
Checks for usage of banned macros
config_of
Code: L-S30
Default Severity: Warning
Minimum Severity: Help
Checks for typeOf used with configFile when configOf could be used instead
Example
Incorrect
private _name = getText(configFile >> 'CfgVehicles' >> typeOf _vehicle >> 'displayName');
Correct
private _name = getText(configOf _vehicle >> 'displayName');
Explanation
The configOf command is specifically designed to retrieve configuration data for a given object, and is faster and more efficient than using typeOf in conjunction with configFile.
missing_file
Code: L-S32
Default Severity: Warning
Minimum Severity: Help
Checks for missing files referenced in sqf
Explanation
Files should exists
reimplementing_command
Code: L-S33
Default Severity: Help
Minimum Severity: Help
Checks if a code could be replaced with a command
Example
Incorrect
private _newValue = 10 + ((25 - 0) * (100 - 10) / (0 - 0));
Correct
private _newValue = linearConversion [0, 0, 25, 10, 100];
Explanation
Some code patterns can be more efficiently implemented using built-in commands. This lint identifies such patterns and suggests using the appropriate command for better performance and readability.
count_skipable
Code: L-S35
Default Severity: Help (Pedantic)
Minimum Severity: Help
Checks for use of count when findIf could possibly be used instead
### Example
Incorrect
{alive _x} count allUnits > 0 // has to check every single unit even if the first is alive
Correct
allUnits findIf {alive _x} != -1
Explanation
When checking if any elements in an array match a condition, using findIf can be more efficient than count, as findIf can stop searching once a match is found.
Check the wiki to learn more about findIf optimization.
global_var_in_local
Code: L-S36
Default Severity: Error
Minimum Severity: Warning
Checks for use of global variables in private and param variable declarations
### Example
Incorrect
private ["z"];
Correct
private ["_z"];
direct_private
Code: L-S38
Default Severity: Help
Minimum Severity: Help
Checks for private declarations that are followed by a separate assignment
Example
Incorrect
private ["_a"];
_a = 1;
Correct
private _a = 1;
Explanation
Declaring a private variable and assigning it in a separate statement is slower than using a direct initializer. The direct form avoids an extra initialization step.
replaced_functions
Code: L-S39
Default Severity: Warning
Minimum Severity: Help
Checks for usage of BIS functions that have been replaced by commands. The lint will skip any files starting with _bi_ or bis as these are considered modified internal Arma files.
Example
Incorrect
[a,b,c] call BIS_fnc_selectRandom;
Correct
selectRandom [a,b,c];
Explanation
Some BIS functions have been replaced by commands, which are more efficient and easier to read. Using the native commands is recommended for better performance.
string_concat_in_loop
Code: L-S40
Default Severity: Help
Minimum Severity: Help
Checks for repeated string concatenation inside loops
Example
Incorrect
private _myString = "";
for [{_i = 0}, {_i < 10000}, {_i = _i + 1}] do {
_myString = _myString + "123";
};
Correct
private _strings = [];
for [{_i = 0}, {_i < 10000}, {_i = _i + 1}] do {
_strings pushBack "123";
};
private _myString = _strings joinString "";
Explanation
Repeatedly concatenating a string inside a loop can become very slow for large iteration counts. Building an array of fragments and joining them once at the end is typically much faster.
foreach_apply
Code: L-S41
Default Severity: Help (Disabled)
Minimum Severity: Help
Checks for forEach loops where _forEachIndex is never used
Example
Incorrect
{
_values pushBack _x;
} forEach bigArray;
Correct
bigArray apply {
_values pushBack _x;
};
Explanation
When a forEach loop never uses _forEachIndex, it can often be replaced with apply, which is the more idiomatic and typically faster form for array iteration.
for_range
Code: L-S42
Default Severity: Help
Minimum Severity: Help
Checks for for [{},{},{}] loops, where for .. from .. to .. do could be used instead
Example
Incorrect
for [{_i = 0}, {_i < 10}, {_i = _i + 1}] do {
// code
};
Correct
for _i from 0 to 10 do {
// code
};
Explanation
For loops using for [{},{},{}] are less efficient than using for .. from .. to .. do. The latter is more readable and performs better, as it avoids the overhead of evaluating the loop condition and increment expressions on each iteration.
array_setcount
Code: L-S43
Default Severity: Help
Minimum Severity: Help
Checks for appending to an array with _array set [count _array, _value]
Example
Incorrect
_a set [count _a, _value];
Correct
_a pushBack _value;
Explanation
Using set [count _array, value] to append to an array is less clear and less idiomatic than pushBack. The latter communicates the intent directly and is the standard way to append values.
string_concat_format
Code: L-S44
Default Severity: Help (Disabled)
Minimum Severity: Help
Checks for string concatenation chains and suggests format
Example
Incorrect
private _person = _first + " " + _last + ", " + str _age + " years old";
Correct
private _person = format ["%1 %2, %3 years old", _first, _last, _age];
Explanation
Chaining string concatenation with + can be harder to read and maintain than using format, especially when values are mixed with literals.
array_append
Code: L-S45
Default Severity: Help
Minimum Severity: Help
Checks for extending an array with _array = _array + [...]
Example
Incorrect
MY_array = MY_array + ["a", "b"];
Correct
MY_array append ["a", "b"];
Explanation
array1 + array2 builds a brand new array and the assignment rebinds the variable to it, so the
whole array is copied every time it is extended. append adds to the existing array in place.
This changes behaviour when something else still holds a reference to the original array, because
arrays are references and only + produces a copy:
private _b = MY_array;
MY_array = MY_array + ["a"]; // _b keeps the old array, no "a"
MY_array append ["a"]; // _b sees "a", it is the same array
Only reported when the right hand side is an array literal, so numeric and string + are not
affected.