Updated:
Jan 21, 2024
- Warning Changing Unreal Code
- How to Use This Guide
- General Warning
- About Clang Tidy
- General
- Helpful Links
- Setup
- Suppressing Checks
- VSCode Extension Guide
- clangd
- Copy Check Name
- Quick Fix
- Refreshing Source File
- Running clang tidy on multiple files
- unreal-clangd
- Context Menu
- clangd
- Trouble Shooting
- Tidy: Unreal Macros
- GENERATED_BODY
- GENERATED_UCLASS_BODY
- GENERATED_UINTERFACE_BODY
- IMPLEMENT_PRIMARY_GAME_MODULE
- IMPLEMENT_MODULE
- check() and other check...() functions
- Tidy: Check Warnings
-
Be careful on changing code created by Epic.
-
You never know if there's a strange Unreal or "Game Programming" reason on why it is the way it is.
-
Here's a Check where I had an Exception when
changing the code
: misc-use-anonymous-namespace -
Here's another reason to be careful when changing Unreal code:
From botman(reddit):
Be aware that some of the "unusual" code choices by Epic that are typically pointed out by linters or static analysis
are to handle the different compilers for all the supported platforms
(Xbox, Playstation, Switch, Android, IOS, Win64, etc).
The easiest way is to just search the name of the Check that you're getting the warning on.
See Check Docs quick access on how to find the name to search for.
The Check warnings, listed below in this guide, follow this pattern:
- Check: (name)
- Warning message:
- Example:
- Notes:
- Recommendation(s)
Note:
The recommendations can have multiple choices which means it's up to you to pick the best choice.
- I'm not a C++, Unreal, or clang tidy expert. This is opinion based on what I currently know or researched.
- Some opinions might change with different context so the recommendations could be wrong for your situation
- For example: There are a couple of times, with Lyra, I suppressed warnings because there were C++ comments that applied to the warning.
Note:
Please use Issues or Pull requests to help with any incorrect info or to add any warnings you get in your Unreal code
clang-tidy is a clang-based C++ “linter” tool. Its purpose is to provide an extensible framework for diagnosing and fixing typical programming errors, like style violations, interface misuse, or bugs that can be deduced via static analysis.
https://clang.llvm.org/extra/clang-tidy/
- This was created with the free, Epic made, Lyra example project for Unreal 5.3
- It can be used as a general guide for other projects
- This was tested with VSCode using the clangd extension
- Other extensions/IDEs can also use Clang Tidy
- But they
won't
be able to use the.clangd
config file - You'll have to substitue it for
Comment
suppresion or.clang-tidy
suppression
- But they
- Unreal-clangd
- My VSCode helper extension for Unreal/clangd
- Helps setup a clangd config for Unreal (with clang-tidy option)
- Tidy config is default off so you need to turn it on (or see Setup)
- Takes over
Intellisense
andformatting
from the Microsoft's C++ extension note:
You can still use Microsoft's C++ extension for debugging
- Clang Tidy docs
- Clangd config docs - More targeted suppression of Clang Tidy warngins using .clangd config
- Epic C++ Coding Standard (UE 5.3)
If you're using my VSCode extension Unreal-clangd you can turn on clang-tidy
by just creating the clang-tidy config file for your project.
Note:
If your VSCode extension or other IDE supports clang-tidy, see their documentation on how to get it to work.
Setup:
- Create a .clang-tidy file in your project's parent directory
- Add this to your file(keep all formatting the same):
--- Checks: |- -*, readability-*, cppcoreguidelines-*, bugprone-*, modernize-*, performance-*, misc-* CheckOptions: - key: readability-function-cognitive-complexity.Threshold value: 25 FormatStyle: file # Keep blank line at end of file
The file has 3 sections: Checks, CheckOptions, and FormatStyle
This section is where you can enable and disable checks Globally
.
The first line "-*," is used to first disable every default enabled check.
We then enable different categories and enable all their Checks using the '*' character,
readability-*,
cppcoreguidelines-*,
bugprone-*,
modernize-*,
performance-*,
misc-*
This is also where you can disable specific checks globally.
Example:
readability-*,
-readability-uppercase-literal-suffix,
cppcoreguidelines-*,
You can see the specific check, readability-uppercase-literal-suffix
, is disabled by using a '-' prepended to it.
This is an example on how CheckOptions are formatted.
- In this example, Threshold is a option for the check.
- Threshold's value is the Check Option's default value.
CheckOptions are another way to suppress Check warnings using a specific option of a Check.
Used for Tidy 'Quick Fix' formatting
The file value means we're using a .clang-format file. My VSCode extension Unreal-clangd will auto create this file.
Note:
If you're not using a .clang-format file you can change the value to llvm
or google
.
If you hover over a check, a window will pop up with the Check as a link you can click. Click the link to go to the documenation for the specific check.
- The link in blue is also the name you want to search this guide for
Note:
The check's page will sometimes show you the Check's advanced Options
you can set in the CheckOptions
section of .clang-tidy or .clangd config files.
We'll be using five ways
to suppress clang-tidy checks
-
You can have multiple .clang-tidy files. Can be used for
Global
orDirectory
suppression of checks. Which ever .clang-tidy file isclosest
to your source file is the file that will be used.I'll only use one .clang-tidy file for
Global
suppression of checks.Example suppression of a Check using a prepended '-' character:
readability-*, -readability-uppercase-literal-suffix, cppcoreguidelines-*,
-
You can have multiple .clangd files. Can be used for
Global
,Directory
,File(s)
orPer Check
. We will be doingPer Check
By Adding more sections to your
.clangd
file you can suppressper check
for multiple files.Note:
.clangd files don't support backslash in paths# Rest of file above here --- # 3 dashes create a new section If: PathMatch: - Source/LyraGame/LyraGameplayTags.cpp - Source/LyraGame/LyraGameplayTags.h - Source/LyraGame/LyraLogChannels.cpp - Source/LyraGame/LyraLogChannels.h Diagnostics: ClangTidy: Remove: - cppcoreguidelines-avoid-non-const-global-variables # Other Sections below here
-
You can use comments to prevent clang-tidy from parsing your code.
-
I'll be using '
// NOLINT
' , '// NOLINTNEXTLINE
' , '// NOLINT(Check-To-Suppress, ...)
' , and '// NOLINTNEXTLINE(Check-To-Suppress, ...)
'- Using the
// NOLINT(Check-To-Suppress)
makes searching for suppressed Checks a lot easier. - I recommend using '(Check-To-Suppress)' on everything except macros
- Using the
-
There's also
'// NOLINTBEGIN(Check-To-Suppress, ...)'
and matching'// NOLINTEND(Check-To-Suppress, ...)'
but we won't be using them. See Tidy docs for more info.
Note:
Many won't likeComment
suppression, but on certain Checks it's unavoidable.For example, the Check that finds variables that haven't been initialized. You want to disable false Check warnings with
Comment
suppression and not byPer Check
orGlobal
suppression. UsingComment
suppression allows Tidy to continue finding variables that haven't been initialized throughout the file or project. -
-
Some
Checks
have options, that you can set, that also suppress. You can find these options in the documentation for the specific check.You can use CheckOptions in both config files:
CheckOptions: - key: misc-non-private-member-variables-in-classes.IgnorePublicMemberVariables value: true
--- If: PathMatch: - Source/LyraGame/AbilitySystem/LyraAbilitySystemComponent.cpp Diagnostics: ClangTidy: CheckOptions: cppcoreguidelines-pro-type-member-init.IgnoreArrays: true
-
Of course another way to suppress Checks is to change the code.
- Must be careful since this can cause problems
- A lot of times you shouldn't change the code!
- Unreal/Game programming sometimes do things differently than the norm
- See warning section!
- Some code can be auto fixed by clang Tidy
- Must be careful though! Again because a lot of times you shouldn't even do it.
- Also Tidy might do it incorrectly. For example: using std::move instead of Unreal's way which is MoveTemp(). So if you do Quick Fix, make sure to fix any Unreal incompatibility mistakes it's made.
- To access Quick Fix, hover over the error and when the window pops up choose Quick Fix
This is a guide for some VScode extensions people might be using.
In VSCode, hover over the warning will pop up a window:
In this window you can:
- Copy the Check name (it's tricky but can be done)
- Use the
Quick Fix
ability to change the code
Changing a Tidy config file will not update the warnings list. You can refresh a source file by:
- Typing a space in the source file
- Waiting half a second
- Deleting the space
- Alternatives are:
- Press F1 and use the Reload Window command
- Or just restart VSCode
-
Just keep double clicking on all the files you want to check
-
All the files, opened by double clicking, will now process Intellisense and Clang Tidy in parallel
-
I've also used this extension to do the same thing(be careful with recursion though!):
https://marketplace.visualstudio.com/items?itemName=LuanEduardoCosta.vscode-open-files
Right Click on your document to open a context menu:
- Allows you to paste
Comment
suppression on the line the cursor is currently on. - Context Menu also has an option to test Clang Tidy
- It's near the bottom of the Context Menu
- Paste in the
global namespace
, in one of your source files, to test if Tidy is working
- I've had trouble with .clangd/tidy config files
not
working whennot
putting aempty line
at theend of the file
. - Mising comma after one or more of the entries in your
.clang-tidy
file (I've had this happen)
- Make sure there is a minus '
-
' sign in front of the check you're trying to suppress in your.clang-tidy
file - Some checks have
double checks
and come from different Tidy libraries. You might think you didn't suppress, but you did. It's just that the same Check from another category took over. - On
Windows
, PathMatch section in .clangd needs to use '/
' for paths.
It's rare, but clang-tidy can crash!
In the past, in my extension, I had added a Global
suppression because of a crash in Lyra. This happened in UE 5.1 with Lyra.
readability-*,
-readability-static-accessed-through-instance,
cppcoreguidelines-*,
This was wrong to do!
If anything it should've been a Per Check
or Comment
(if a user were doing it). I believe the code could have also been changed.
Note:
In Lyra using UE 5.3, this Check no longer crashes.
For Unreal macros we use tidy Comment
suppression to prevent Tidy parsing.
- This is because Macros can have multiple checks
- This is also because Macros can cause errors that wouldn't normally occur in normal code. So you'd want to suppress these errors only for the Macro.
I use the general comment to suppress Check warnings in Macros:
GENERATED_BODY() // NOLINT
List of Macros that caused warnings and were ignored with Comment suppression:
GENERATED_BODY
GENERATED_UCLASS_BODY
GENERATED_UINTERFACE_BODY
IMPLEMENT_PRIMARY_GAME_MODULE
IMPLEMENT_MODULE
check()
and other check...() functionsensure
sometimes because of the parameters
Note:
check() and other check...() functions are also mentioned below. They probably shouldn't have been though. I'll still leave them up since people might not realize they are macros.
These are all the warning Checks found in Lyra(UE 5.3). Hopefully other devs will add warning Checks found in their code!
They follow a pattern of:
- Check: (name)
- Warning message:
- Example:
- Notes:
- Recommendation(s)
Use a trailing return type for this function
FString GetClientServerContextString(UObject* ContextObject)
{
UFUNCTION doesn't support this so we don't. Whenever this is supported then this will be categorized as project choice.
Global suppression
(.clang-tidy)
Narrowing conversion from 'double' to 'float'
- Can be other types
- I believe Epic ignores these when Building. Ignore Globally or Per Check if you want.
-
Global
suppression -
Per Check
suppression. Allows you to keep track of which files have this warning. -
Special Comment Supression
Using Tidy allows us to suppress the Checks and also allows developers to find them easily. Narrowing conversions can cause multiple Tidy warnings so we use
Tidy's special character
to suppress any warning ending with 'narrowing-conversions'.// NOLINT(*narrowing-conversions)
100000.0f is a magic number; consider replacing it with a named constantclang-tidy
Tidy's default is to not count 0,1, and 100 as magic numbers. You can change this with a CheckOption
.
Magic numbers might not be that big a deal in the context of:
https://docs.unrealengine.com/5.3/en-US/configuration-files-in-unreal-engine/
-
Global
suppression -
Per Check
suppression with a check option ignore. Allows you to keep track of files/values- This also allows you to use a
CheckOption
targetting a specific file or files with a custom list of allowed Magic Numbers
- This also allows you to use a
-
Comment
suppressUsing Tidy allows us to suppress the Checks and also allows developers to find them easily. Magic Numbers can cause
multiple
Tidy checks so we use Tidy'sspecial
comment character '*
' to suppress any Check that ends with 'magic-numbers'.// NOLINT(*magic-numbers)
Use auto when initializing with a template cast to avoid duplicating the type name
if (AActor* Actor = Cast<AActor>(ContextObject))
See here:
https://docs.unrealengine.com/5.3/en-US/epic-cplusplus-coding-standard-for-unreal-engine/#auto
Always test! See warning section.
-
Global suppression
(.clang-tidy) -
Per Check
-
Change code
(can Tidy quick fix)Tidy quick fix example:
if (auto* Actor = Cast<AActor>(ContextObject))
Note:
If we adhere to the Unreal Code Stardard this wouldn't be allowed. I believe it should be though since the Cast clearly shows the type.
For other context that don't clearly show the type then I wouldn't change the code.
'virtual' is redundant since the function is already declared 'override'
virtual void StartupModule() override
{
Epic's projects still use virtual
Always test! See warning section.
Global suppression
(.clang-tidy)Change code
(Needs testing)
Variable 'TestVar' is non-const and globally accessible, consider making it const
Of course you always want to try to make a var const if possible but with game dev sometimes you do things unorthodox. Lyra has this warning a lot.
-
For Lyra I would do it
Per Check
in .clangd file.This is because of Lyra's Gameplay Tags/Ability system
Allows you do get the warning, acknowledge it, and then suppress it.
-
Use best judgement
. There are, of course, times though you may want to fix the code.
UE_LOG(LogLyra, Display, TEXT("Could not find exact match for tag [%s] but found partial match on tag [%s]."), *TagString, *TestTag.ToString());
Global
suppression(.clang-tidy)
Do not use 'else' after 'return'
if (Role != ROLE_None)
{
return (Role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
}
else
{
#if WITH_EDITOR
if (GIsEditor)
{
Always test when changing code! See warning section.
-
Use best judgement
-
There might be code where you suppress this but, for
this instance
, wechange
the code and remove the else (Tidy can auto fix this) -
There are other instances in Lyra that also give this check warning.
Parameter 'WorldType' is const-qualified in the function declaration; const-qualification of parameters only has an effect in function definitions
Always test when changing code! See warning section.
Global
suppressionChange
the code
Suspicious #include of file with '.cpp' extension
#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraGamePhaseSubsystem)
Global
(.clang-tidy) suppressionComment
suppression
Use '= default' to define a trivial default constructor
Epic knows best? One of those where you don't question Epic but remind yourself to research some time in the future.
Per Check
suppression to allow you to find and test these in the future
if (FoundSpec && FoundSpec->IsActive())
{
Always test when changing code! See warning section.
Global(.clang-tidy) suppression
Change code
(can Tidy quick fix)
Here's what Tidy quick fix
does:
if ((FoundSpec != nullptr) && FoundSpec->IsActive())
{
Do not use const_cast
One of those game dev is not the same as C++ dev things...
Per check
suppression to keep track of which files have these(.clangd)Global
suppression(.clangd)
The parameter 'PhaseEndedCallback' is copied for each invocation but only used as a const reference; consider making it a const reference
void ULyraGamePhaseSubsystem::StartPhase(TSubclassOf<ULyraGamePhaseAbility> PhaseAbility, FLyraGamePhaseDelegate PhaseEndedCallback)
{
- Context matters on this warning!
- Need to research/test if changing code is a viable option
- More than likely, in this Lyra instance the CheckOptions suppression is the right call
- This is because FLyraGamePhaseDelegate is a delegate type created by the DECLARE_DELEGATE_OneParam macro
- In your own code it might not have this context. It could be that changing the code it the right call
- Always test when changing code! See warning section.
- Context matters
CheckOption
suppression- The 'AllowedTypes' Check Option allows you to specificy a semicolon-sperated list of names of types allowed to be passed by value.
Change
code if the context warrants. Code that deals with custom types should be looked at more carefully and shouldn't be changed without proper thought.- Always research and test which option is best
Redundant boolean literal in conditional return statement
Boolean expression can be simplified by DeMorgan's theorem
if (ConnectionType == EOnlineServerConnectionStatus::Normal || ConnectionType == EOnlineServerConnectionStatus::Connected)
{
return true;
}
return false;
- Some Unreal
ensure...
macros give this warning and can be suppressed - Always test when changing code! See warning section.
Simplify the code
Comment Suppression
.
You determine the best course of action. Most often I would simplify the boolean expression. There are times where I would keep it though so it's up to you. For example, one error where this occurred had some commented code inside the 'if' statement. I 'Comment Suppressed' the warning because of this.
With another one of these errors, I did simplify.
Example with quick fix:
return ConnectionType == EOnlineServerConnectionStatus::Normal || ConnectionType == EOnlineServerConnectionStatus::Connected;
note
: It can also be choice:
It will want to change this:
if (RepList.RemoveFast(ActorInfo.Actor) == false)
To this:
if (!RepList.RemoveFast(ActorInfo.Actor))
You might think it's more readable the first way.
Replace loop by 'std::ranges::any_of()'
for (const auto& KVP : ActivePhaseMap)
{
Even though there has been some easing of the use of some of the standard library, 'For loops' are still being recommened to be done like the above example.
Global
suppression in .clang-tidy
Function 'MyFunction' has cognitive complexity of 43 (threshold 25)
This warning is about the complexity of a function.
-
Use a global
CheckOption
to up thethreshold
to whatever you think is best. -
Comment Suppress any function that goes over the
threshold
setting so you can make note of functions that may need looking after.
Check Option example:
- key: readability-function-cognitive-complexity.Threshold
value: 150
- Comment Suppress example:
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void UMySubsystem::OnBeginComplexFunction(const UAbility* MyAbility, const FSpecHandle MyHandle)
{
Parameter 'MyParam' is unused
Function parameters are often unused with Unreal game dev
Global
suppression in .clang-tidy
Redundant access specifier has the same accessibility as the previous access specifier
protected:
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
protected:
// Defines the game phase that this game phase ability is part of. So for example,
In Unreal, redundant specifiers are often used to organize
Global
suppression in .clang-tidy
Member variable 'Foo' has protected visibility
protected:
int32 Foo;
Lyra other Unreal projects still use protected class vars.
Global
suppression in .clang-tidy- Keep an eye out for change in coding styles. Maybe protected vars won't always be ok for Unreal game dev.
Floating point literal has suffix 'f', which is not uppercase
float BaseHeal = 0.0f;
Lower case 'f' for floating point literals are the default choice in Lyra and most Unreal projects.
Always test when changing code! See warning section.
Global
suppression in .clang-tidyChange
the code(Tidy quick fix available)
Function 'HealStatics' declared 'static', move to anonymous namespace
static FHealStatics& HealStatics()
{
static FHealStatics Statics;
return Statics;
}
I tried changing the code. Build was successful but caused an Exception!
.
Global suppression
(.clang-tidy)Per Check suppression
(.clangd)
'bOutOfHealth' should be initialized in an in-class default member initializer
ULyraHealthSet::ULyraHealthSet()
: Health(100.0f)
, MaxHealth(100.0f)
{
bOutOfHealth = false;
Lyra seems to like both.
There was a pattern building where UPROPERTY variables would be in the initializer list and non-UPROPERTY variables would be initialized in the constructor. I ran into a case where that pattern stopped.
Global
Suppress and just have a clear rule.
Function 'GetLyraAbilitySystemComponent' should be marked [[nodiscard]]
In file Source\LyraGame\AbilitySystem\Attributes\LyraAttributeSet.h
ULyraAbilitySystemComponent* GetLyraAbilitySystemComponent() const;
Always test when changing code! See warning section.
Change
the code.[[nodiscard]] ULyraAbilitySystemComponent* GetLyraAbilitySystemComponent() const;
Global
suppression if you don't care about this warning
Function-like macro 'ATTRIBUTE_ACCESSORS' used; consider a 'constexpr' template function
Variadic macro 'IMAGE_BRUSH' used; consider using a 'constexpr' variadic template function
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
-
For most Unreal instances I'd use the
Global CheckOption
to exclude the macro name in .clang-tidy.I would definitely add any other Unreal macros, that gives this error, to the value string. For
multiple macro names
in the value string, separate them using the '|
' character.- key: cppcoreguidelines-macro-usage.AllowedRegexp value: "ATTRIBUTE_ACCESSORS|LOCTEXT_NAMESPACE"
-
Could also do
Global
orPer Check
suppression
Use 'using' instead of 'typedef'
typedef TFunctionRef<bool(const ULyraGameplayAbility* LyraAbility, FGameplayAbilitySpecHandle Handle)> TShouldCancelAbilityFunc;
Don't
use 'using' in the global scopenote:
See here for more 'using' rules https://docs.unrealengine.com/5.3/en-US/epic-cplusplus-coding-standard-for-unreal-engine/#namespaces- Always test when changing code! See warning section.
-
Per Check
suppression -
Comment
suppression -
Global
suppression -
Change
code? Ceck the "Notes:" above
Do not declare C-style arrays, use std::array<> instead
int32 ActivationGroupCounts[(uint8)ELyraAbilityActivationGroup::MAX];
Notice it's a small array of int32. Game dev will have you doing things different and this is one example. Lyra uses them for light weight arrays more than once.
Per Check
in your .clangd file to keep track of which files have this warningComment
suppression
if (AController* PC = CurrentActorInfo->PlayerController.Get())
{
CheckOption has both .IgnoredVariableNames
and IgnoredParameterNames
. It also many more CheckOptions if you want to check the docs quick access
-
Change
code. You can have short names for variables that are known but should think about changing confusing short named variables. -
CheckOption
regex that lets you add names to ignore(good for short names known to Unreal devs).Example:
- key: readability-identifier-length.IgnoredVariableNames value: "PC|C|LP" - key: readability-identifier-length.IgnoredParameterNames value: "Ar|PC|YL"
Initializer for base class 'FGameplayEffectContext' is redundant
FLyraGameplayEffectContext()
: FGameplayEffectContext()
{
}
Always test when changing code! See warning section.
Global
ignoreChange
the code(as long as Tidy is correct)
Initializing non-owner 'FLyraGameplayEffectContext *' with a newly created 'gsl::owner<>'
FLyraGameplayEffectContext* NewContext = new FLyraGameplayEffectContext();
Another from LyraMemoryDebugCommands.cpp:
delete OutputFile;
From Source\LyraGame\AbilitySystem\LyraGameplayEffectContext.h
I believe this gets put into Unreal's garbage collection system.
Always test when changing code! See warning section.
Epic sees no problem using new and delete for specific noncommon stuff
Per Check
in .clangdComment
suppressionChange
code if it's warranted.- Use new/delete sparingly and carefully
- You can search Lyra on how Epic used it
- Most devs won't need to
2 adjacent parameters of 'MyFunction' of similar type ('MyType') are easily swapped by mistake
The two FGameplayTag parameteres are marked by Tidy
HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState)
- Leave Epic's functions alone
- Think about how you design your own functions and if you care about this.
Check option
, IgnoredParameterNames, to ignore certain parameter names (there are a bunch of Check options)- Or just
Global suppression
in .clang-tidy
Do not access members of unions; use (boost::)variant instead
Global
suppression
Avoid repeating the return type from the declaration; use a braced initializer list instead
if (ASC->HasMatchingGameplayTag(TAG_Gameplay_MovementStopped))
{
return FRotator(0,0,0);
}
Always test when changing code! See warning section.
-
Global
suppression -
Change
code(Tidy quick fix)if (ASC->HasMatchingGameplayTag(TAG_Gameplay_MovementStopped)) { return {0,0,0}; }
All parameters should be named in a function
void ALyraCharacter::OnDeathStarted(AActor*)
-
Change
code(can Tidy quick fix)Tidy Quick Fix pastes a /*unused*/ for the parameter which fixes the warning:
void ALyraCharacter::OnDeathStarted(AActor* /*unused*/)
-
Global
suppression
Multiple declarations in a single statement reduces readability
double AccelXYRadians, AccelXYMagnitude;
These are used as OUT variables and are set on the next line
Per Check
Global
Comment
Variable 'AccelXYRadians' is not initialized
double AccelXYRadians, AccelXYMagnitude;
These are OUT variables that are set on the next line
-
Comment
suppressionUnfortunately this will be a comment suppression recommendation. You definitely want to be warned of variables that aren't initialized.
The above example is a mistake since these are two OUT variables that are set on the next line. So we Comment suppress like so:
double AccelXYRadians, AccelXYMagnitude; // NOLINT(cppcoreguidelines-init-variables)
Method 'MyFunction' can be made const
Use your own best judgement on changing your function to a const function.
Doesn't seem like it would hurt anything...
Always test when changing code! See warning section.
Change
code
Method 'GetDesiredThread' can be made static
ENamedThreads::Type GetDesiredThread() { return ENamedThreads::GameThread; }
Always test when changing code! See warning section.
Change
code (Didn't seem like it hurt)Per Check
Global
Do not use C-style cast to downcast from a base to a derived class; use dynamic_cast instead
if ((BaseEffectContext != nullptr) && BaseEffectContext->GetScriptStruct()->IsChildOf(FLyraGameplayEffectContext::StaticStruct()))
{
return (FLyraGameplayEffectContext*)BaseEffectContext;
}
- You aren't suppose to use dynamic_cast. If you need to use dynamic_cast you can use Cast<>()
- This isn't to say you should change this
- This could be one of those 'game programming' or Unreal situations
- Example: It does check
IsChildOf()
in theif
statement
- Example: It does check
- Always test when changing code! See warning section.
Per Check
suppression. You want the warning, in other files, but don't want to use Comment Suppression.Comment
suppression. You want to be warned. In this case it's probably fine since the 'If' statement does check IsChildOfChange
code when warranted.- Always research and test
- Use Cast<>() instead of dynamic_cast
Use default member initializer for 'CartridgeID'
FLyraGameplayAbilityTargetData_SingleTargetHit()
: CartridgeID(-1)
{ }
virtual void AddTargetDataToContext(FGameplayEffectContextHandle& Context, bool bIncludeActorArray) const override;
/** ID to allow the identification of multiple bullets that were part of the same cartridge */
UPROPERTY()
int32 CartridgeID;
Always test when changing code! See warning section.
-
Global
suppression -
Change
code (Didn't seem like it hurt anything)FLyraGameplayAbilityTargetData_SingleTargetHit() { } virtual void AddTargetDataToContext(FGameplayEffectContextHandle& Context, bool bIncludeActorArray) const override; /** ID to allow the identification of multiple bullets that were part of the same cartridge */ UPROPERTY() int32 CartridgeID{-1};
Constructor does not initialize these fields: ActivationGroupCounts
You don't
want to suppress this globally. You'll want to know if non-UPROPERTY variables aren't being initialized.
One time we got this because of an array. It was initialized in the constructor with FMemory::Memset
Multiple options depending on context
-
Per Check
Check option
to ignore arrays and mark files that have this warning. -
Comment
suppression if you don't mind commentsPer Check example in .clangd:
--- If: PathMatch: - Source/LyraGame/AbilitySystem/LyraAbilitySystemComponent.cpp Diagnostics: ClangTidy: CheckOptions: cppcoreguidelines-pro-type-member-init.IgnoreArrays: true
-
Per Check
suppression -
Comment
suppressionPer Check example:
--- If: PathMatch: - Source/LyraGame/Camera/LyraUICameraManagerComponent.cpp Diagnostics: ClangTidy: Remove: - cppcoreguidelines-pro-type-member-init
Do not implicitly decay an array into a pointer; consider using gsl::array_view or an explicit cast instead
FMemory::Memset(ActivationGroupCounts, 0, sizeof(ActivationGroupCounts));
ActivationGroupCounts is an array
I'm sure it's fine to suppress in the example context.
Per Check
suppression. To keep track of what files have this warning.Global
suppression if you don't care to keep trackComment
suppression
Do not use array subscript when the index is not an integer constant expression
ActivationGroupCounts[(uint8)Group]++;
Per Check
suppression in .clangd to keep track of all files that have the warningGlobal
suppression if you don't care to trackComment
suppression if you don't care about comments.
Destructor of 'ILyraAbilitySourceInterface' is protected and virtual
class ILyraAbilitySourceInterface
{
GENERATED_IINTERFACE_BODY() // NOLINT
Has to do with GENERATED_IINTERFACE_BODY() macro. The macro defines the destructor as protected and virtual.
Per Check
suppression to keep track of files that have this warningGlobal
suppression
Class 'ILyraAbilitySourceInterface' defines a non-default destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move assignment operator
class ILyraAbilitySourceInterface
{
GENERATED_IINTERFACE_BODY()
Has to do with GENERATED_IINTERFACE_BODY() macro.
Global
suppression. Epic designs these macros.
Argument name 'bCreateOnFail' in comment does not match parameter name 'bCreateProfileOnFail'
Function arg:
..., /*bCreateOnFail=*/ false))
Change
comment to correct name
Avoid do-while loops
do
{
NewIndex = (NewIndex + 1) % Slots.Num();
Of course if you don't have to use a do-while loops then don't
Global
suppression
Function 'ULyraEquipmentManagerComponent::EquipItem' has a definition with different parameter names
// .h
ULyraEquipmentInstance* EquipItem(TSubclassOf<ULyraEquipmentDefinition> EquipmentDefinition);
// .cpp
ULyraEquipmentInstance* ULyraEquipmentManagerComponent::EquipItem(TSubclassOf<ULyraEquipmentDefinition> EquipmentClass)
{
Change
code. Make parameter names the same. Doesn't matter which one you choose but choose one.
Result of integer division used in a floating point context; possible loss of precision
const float NumberYOffset = ((NumberIndex / FMath::Max(1, DamageNumberArrayLength - 1)) - 0.5f) * 2.f;
Always test when changing code! See warning section.
Per Check
suppression to keep track of which files have this warningComment
suppression for more precise monitoringChange
code if you need to but remember Unreal/Game Programming can do things unconventional! This is most likely ok.
'const auto Component' can be declared as 'auto *const Component'
for (const auto Component : OwningActor->GetComponents())
{
Code change doesn't seem like it would hurt.
Always test when changing code! See warning section.
-
Change
code(Tidy quick fix)for (auto *const Component : OwningActor->GetComponents()) {
Return type 'const float' is 'const'-qualified at the top level, which may reduce code readability without improving const correctness
const float ULyraAimSensitivityData::SensitivtyEnumToFloat(const ELyraGamepadSensitivity InSensitivity) const
{
Always test when changing code! See warning section.
Change
code to remove first const from definition/implementationGlobal
supppression if you don't care about this warning
Change code example:
.cpp
float ULyraAimSensitivityData::SensitivtyEnumToFloat(const ELyraGamepadSensitivity InSensitivity) const
{
Member 'Options' of type 'TArray<FInteractionOption> &' is a reference
private:
TScriptInterface<IInteractableTarget> Scope;
TArray<FInteractionOption>& Options;
This message appeared on a class called FInteractionOptionBuilder. A Builder class, especially in games, might be created differently.
Always test when changing code! See warning section.
Per Check
suppress so you can keep track of which file the warning wasChange
code if you think it makes sense
Pass by value and use std::move
Constructor:
class FGameSettingEditCondition_VideoQuality : public FGameSettingEditCondition
{
public:
FGameSettingEditCondition_VideoQuality(const FString& InDisableString)
: DisableString(InDisableString)
- MoveTemp() is Unreal's std::move equivalent so use that instead
- Remember to always test.
- Quick Fix works but will use std::move so you'll have to change it to MoveTemp()
- Always test when changing code! See warning section.
Change
code if possible and warrantedPer Check
suppression to know what files have the warningComment
suppression for more precision
This seems to be fine:
class FGameSettingEditCondition_VideoQuality : public FGameSettingEditCondition
{
public:
FGameSettingEditCondition_VideoQuality(FString InDisableString)
: DisableString(MoveTemp(InDisableString))
Constness of 'AbsolutePath' prevents automatic move
const FString AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*LogFilename);
return AbsolutePath;
Is Epic doing this on purpose or is it a bug?
Always test when changing code! See warning section.
Change Code
and remove const?
Checking other Lyra functions that return FString shows that this is rare. Doesn't seem like removing const would be a problem...
Always test though!
Initializing non-local variable with non-const expression depending on uninitialized non-local variable 'GWorld'
Lambda as a parameter:
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(
[](const TArray<FString>& Params, UWorld* World)
{
GWorld:
const FString CollectionName = FString::Printf(TEXT("_LoadedAssets_%s_%s%s"), *FDateTime::Now().ToString(TEXT("%H%M%S")), *GWorld->GetMapName(), *CollectionNameSuffix);
LogLyra:
UE_LOG(LogLyra, Warning, TEXT("Wrote collection of loaded assets to %s"), *CollectionFilePath);
- I believe LogLyra is incorrectly marked.
- There's also a LogTemp that also seems incorrectly marked. Any errors with Log Categories can be ignored.
- GWorld is a different story.
- This is the only use of GWorld in Lyra
- If you hover over GWorld, it tells you to try not using it
- World is sent as a lambda parameter and looks like you can replace GWorld with it
- Always test when changing code! See warning section.
Per Check
suppression to keep track of files that have this warningComment
suppression for more precise tracking.Change
Code for GWorld?- Maybe there's some reason it was done this way?
Switch has 2 consecutive identical branches
case ELyraPlayerConnectionType::Player:
case ELyraPlayerConnectionType::InactivePlayer:
//@TODO: Ask the experience if we should destroy disconnecting players immediately or leave them around
// (e.g., for long running servers where they might build up if lots of players cycle through)
bDestroyDeactivatedPlayerState = true;
break;
default:
bDestroyDeactivatedPlayerState = true;
break;
Show above, this happens because they both have the same "bDestroyDeactivatedPlayerState = true;"
Note:
This is different though because of the comment signifying that a code change is coming.
Always test when changing code! See warning section.
Because of the comment
about code changes, we suppress.
Per Check
suppressionComment
suppression for more precise tracking
If no comment
about upcoming code changes:
Change
the code
Nested namespaces can be concatenated
namespace Lyra
{
namespace Input
{
Always test when changing code! See warning section.
Change
code(Quick fix available)
What quick fix does:
namespace Lyra::Input
{
Note:
The namespace will need to be formatted after the quickfix. Make sure you have "NamespaceIndentation: All
" in your .clang-format file.
Loop variable is copied but only used as const reference; consider making it a const reference
void ULyraSettingKeyboardInput::RestoreToInitial()
{
for (TPair<EPlayerMappableKeySlot, FKey> Pair : InitialKeyMappings)
{
- In this instance, it seems Tidy is correct and you can change the code
- You'll have to determine what's right for your code
- Always test when changing code! See warning section.
-
Change
code(Quick fix available)What quick fix does:
void ULyraSettingKeyboardInput::RestoreToInitial() { for (const TPair<EPlayerMappableKeySlot, FKey>& Pair : InitialKeyMappings) {
Duplicate include
Change
code(Quick fix available)
'if' statement is unnecessary; deleting null pointer has no effect
if (DefaultContextInternal)
{
delete DefaultContextInternal;
}
Seems ok to change code
Always test when changing code! See warning section.
Change
code(Quick fix available)
Quick fix removes 'if' statement but leave the delete:
delete DefaultContextInternal;
Function 'ProcessLoginRequest' is within a recursive call chain
Per Check
suppression to mark files that have thisComment
suppression for more precise marking
Do not use static_cast to downcast from a base to a derived class; use dynamic_cast instead
if (FGameplayAbilityTargetData_SingleTargetHit* SingleTargetHit = static_cast<FGameplayAbilityTargetData_SingleTargetHit*>(LocalTargetDataHandle.Get(i)))
{
- You're not suppose to use dynamic_cast in Unreal projects. You can use Cast<>() instead.
- This isn't to say you should change the code.
- This could be one of those it's 'game programming' or Unreal situations
- This could have already been vetted by Epic and shown to be fine
- Always test when changing code! See warning section.
-
Per Check
suppress to mark files -
Comment
suppress for more precise targeting -
Test
using Cast<>() instead? Testing should also include performance!
The const qualified variable 'RedirectorPackageName' is copy-constructed from a const reference; consider making it a const reference
const FString RedirectorPackageName = Params[0];
const FString TargetPackageName = Params[1];
- Don't see any reason not to change code but there could be a good reason why it is the way it is
- Always test when changing code! See warning section.
Change
code but testPer Check
to keep track of files with warningComment
suppression for more precise tracking of warnings
LoadedPackage = LoadPackage(NULL, *DestPackageName, LoadFlags);
- Don't use NULL
- This is the only use of NULL in Lyra's main project code
- Docs of 'LoadPackage' mention the parameter is usually nullptr or ULevel->GetOuter()
- Always test when changing code! See warning section.
Change
code to nullptr
Converting integer literal to bool, use bool literal instead
bool LogLoadingScreenReasonEveryFrame = 0;
-
Can't think of any reason not to change the code to 'false'
-
I say that but ran into this:
if (0)
-
Looked like a way to comment out test code?
-
I used
Comment
suppression for that one -
Always test when changing code! See warning section.
-
Change
code usuallybool LogLoadingScreenReasonEveryFrame = false;
for (int32 VerticeIndex = 0; VerticeIndex < UE_ARRAY_COUNT(Vertices); ++VerticeIndex)
{
- Don't see a reason not to
- As always it could be some 'game programming' or Unreal reason
- Perhaps performance?
- Always test when changing code! See warning section.
-
Change
code but testQuick fix example:
for (const auto & Vertice : Vertices) {
Do not use reinterpret_cast
auto ThunkCallback = [InnerCallback = MoveTemp(Callback)](FGameplayTag ActualTag, const UScriptStruct* SenderStructType, const void* SenderPayload)
{
InnerCallback(ActualTag, *reinterpret_cast<const FMessageStructType*>(SenderPayload));
};
- You'll have to recognize advanced code and let it do its thing
- For you're own code make sure you have a really good reason to use it
- Try not to use it in your own code
Per Check
suppression to mark files that have themComment
suppression for more precise marking
Do not use pointer arithmetic
- This was from a Unreal macro so it's safe to ignore if from there
Per Check
to target files that have the errorComment
suppression for more precise targetingNote:
If not an Unreal macro, then make sure you really need pointer arithmetic
Static member accessed through instance
DisplayDebugManager.SetFont(GEngine->GetSmallFont());
See no reason not to change code but needs to be thoroughly tested.
Change
code- Always test when changing code! See warning section.
What Tidy Quick fix does:
DisplayDebugManager.SetFont(UEngine::GetSmallFont());