How Does Linting Work?
Linter's bundled rulesets are included in the Linter Plugin content folder. Engine and Project plugin folders might be hidden in your engine. To show them, make sure the checkboxes next to Show Engine Content and Show Plugin Content are both checked in your Content Browser's View Options.
Dissecting a Linter Rule Set
Linters are defined by LintRuleSets
, essentially a fancy Data Asset. In the above example, we are looking at the MarketplaceLintRuleSet
asset which defines all of the rules we want to use when scanning for marketplace guideline compliance.
In the above image you'll see that this LintRuleSet
uses a NamingConvention
Data Asset named MarketplaceNamingConvention
In addition to a NamingConvention asset, LintRuleSet
assets also contain something called a Class Lint Rules Map. This is a map of Unreal Engine 4 classes paired with a list of LintRules
, which are assets that make up individual rules that we'll cover later.
When objects in an Unreal Engine 4 project are linted, they are scanned using the rules that match the "most specific" class defined in the Class Lint Rules Map. You can use any class in this map, with the base UObject
being a special case.
Unfortunately at this time the Unreal Engine 4 editor does not allow UObject
as a valid value as a key in this Class Lint Rules Map, so if you want to define rules that you want to scan UObjects
with, please use the AnyObject_LinterDummyClass
class instead.
With the above example ruleset, when the Linter comes across a UBlueprint
asset it will scan that asset using the four lint rules defined above. This is because UBlueprint
is a more specific class definition than UObject
. Another asset type, such as a data asset, will instead use the AnyObject_LinterDummyClass
rules when being scanned unless there is a more specific matching class defined in the ruleset than UObject
.
NOTE: Currently there isn't support for allowing cascading rule checks, i.e. allowing a UBlueprint
being scanned against rules paired with UBlueprint
as well as UObject
. This support is planned to be added in a future release. See todo.
How Lint Rules Are Implemented
While LintRules
can be implemented in both Blueprint and C++, currently there aren't too many functions exposed to Blueprint that deal with asset metadata and lower level asset management checks and tasks. It is currently strongly recommended that you do your rule checking logic in C++ and then only expose configuration settings to a Blueprint class.
All of Linter's bundled LintRules
are Blueprint child classes that parent from a native C++ LintRule
, with the goal that the Blueprint LintRules
only expose configuration options.
PassesRule_Internal_Implementation
The core of implementing your own LintRule
is to implement the PassesRule_Internal_Implementation
function. This function can be implemented in either C++ or Blueprint as this is a BlueprintNativeEvent
.
This should be where the business logic of your LintRule
operates. To report a rule violation, push a new FLintRuleViolation
to the OutRuleViolations
array and return false. You should always return false if any rule is violated and you should always return true if no rules were violated. A FLintRuleViolation
is simply a struct that has a reference to the asset that is violating the rule, a reference to the rule that is being violated, and potentially any additional optional recommended text to display to the user reading the Lint Report.
Implementing this function is all you need for your LintRule
to be functional and ready for use. For the sake of example, here is how the Unreal Engine Marketplace Guideline rule for ensuring your textures are not too big is implemented:
bool ULintRule_Texture_Size_NotTooBig::PassesRule_Internal_Implementation(UObject* ObjectToLint, const ULintRuleSet* ParentRuleSet, TArray<FLintRuleViolation>& OutRuleViolations) const
{
const UTexture2D* Texture = CastChecked<UTexture2D>(ObjectToLint);
int32 TexSizeX = Texture->GetSizeX();
int32 TexSizeY = Texture->GetSizeY();
// Check to see if textures are too big
if (TexSizeX > MaxTextureSizeX || TexSizeY > MaxTextureSizeY)
{
FText RecommendedAction = NSLOCTEXT("Linter", "LintRule_Texture_Size_NotTooBig_TooBig", "Please shrink your textures dimensions so that they fit within {0}x{1} pixels.");
OutRuleViolations.Push(FLintRuleViolation(ObjectToLint, GetClass(), FText::FormatOrdered(RecommendedAction, MaxTextureSizeX, MaxTextureSizeY)));
return false;
}
return true;
}
In the above code, we simply check to see if the ObjectToLint
is a texture with width or height exceeding a MaxTextureSizeX/MaxTextureSizeY
, which is defined in our Blueprint child as 8192
. This allows us to easily scan for textures that are bigger than 8k. If we ever want to decrease or increase the size of our textures allowed under this rule, we can easily do so by editing the MaxTextureSizeX/MaxTextureSizeY
in Blueprint without requiring any code changes or code compiling.
It is recommended that you create a Blueprint child of your native classes to fill out the Rule's display info. This way the rule can also have verbiage updates without requiring code edits.
PassesRule Is Most Likely Not What You Want
LintRules
also have a virtual function called PassesRule
. This is not meant for containing the business logic of your LintRule
. This is a public BlueprintCallable
function that allows your LintRule
to have a Blueprint implementation of PassesRule_Internal_Implementation
.
You should only implement this if you want to "early out" of the linting process. Linter's strategy is to try to implement error handling in PassesRule
for things like possible null checks or invalid objects and then only performing the actual scan logic inside PassesRule_Internal_Implementation
. You do not need to implement your own PassesRule
.
IsRuleSuppressed is optional
LintRules
can also be programmatically suppressed by implementing the IsRuleSuppressed
function. This function is called automatically by the base PassesRule
implementation. If you want to simply suppress a rule, do so here instead of PassesRule
.
How Lint Naming Conventions Are Implemented
NamingConvention
assets are simply a list of naming conventions as a data asset. LintRules
will have access to the NamingConvention
data asset that is defined in the lint rule's parent LintRuleSet
. The NamingConvention
data asset isn't responsible for any implementation logic. Instead LintRules
are written to perform these naming convention checks using the given NamingConvention
data asset as configuration.
LintRuleCollections... collect rules
Sometimes it is easier to treat a collection of rules as a single rule. In this case, you can create a LintRuleCollection
class that simply defines a list of other LintRules
. This is very useful when dealing with repetitive path and file name lint rules.
Video Walkthrough of Creating A LintRuleSet
@TODO: Get this edited, uploaded, submitted, embedded
Automated Linting via Commandlets
The Linter plugin adds a Commandlet that you can run against your project via commandline. It will return an error code of 1 if the linting process fails for any reason. It will return an error code of 2 if Linter reports any errors, or warnings as well if -TreatWarningsAsErrors
is passed on the commandline.
To invoke the commandlet, run your Editor binary (i.e. D:\UE424\Engine\Binaries\Win64\UE4Editor-Cmd.exe
), followed by the path to your .uproject
(i.e. "C:\Users\Allar\Documents\Unreal Projects\Linterv2Test\Linterv2Test.uproject"
), followed by the arg -run=Linter
.
Full command for example, D:\UE424\Engine\Binaries\Win64\UE4Editor-Cmd.exe "C:\Users\Allar\Documents\Unreal Projects\Linterv2Test\Linterv2Test.uproject" -run=Linter
.
This will run Linter against the /Game
path, a.k.a. your project's path, and return error code 0 if there are no errors.
Specifying which Lint Rule Set to use
All Lint Rule Sets now have a filed named Commandlet Name
which represents a simple name to identify them via commandline.
The Gamemakin LLC UE4 Style Guide's Commandlet Name
is ue4.style
, where the Unreal Engine Marketplace Guidelines uses the name marketplace
.
To specify this, use the -RuleSet=
arg. For example, -RuleSet=ue4.style
will use the Gamemakin lint rule set. -RuleSet=marketplace
will use the UnrealEngine Marketplace Guidelines. If -RuleSet=
is not provided, Linter will use the project's default Lint Rule Set.
Additional Args
Content Paths
You can tell Linter to scan a list of folders to scan. They should generally be in the UE4 path form of /Game/Content/...
. If a path includes spaces, wrap it in quotes.
For example, if you only wanted to scan folders Apple
and Orange Stuff
inside your project's Content
folder...
D:\UE424\Engine\Binaries\Win64\UE4Editor-Cmd.exe "C:\Users\Allar\Documents\Unreal Projects\Linterv2Test\Linterv2Test.uproject" /Game/Content/Apple "/Game/Content/Orange Stuff" -run=Linter
.
This will scan both the Apple
and Orange Stuff
folders inside your project's Content
folder. You can also pass in Engine
and plugin folders. If no path is provided, the default will always be /Game
.
JSON Report
To generate a .json
report, you can add the switch -json
to generate a .json
report in your project's Saved/LintReports/
folder. You can override the name of the report via -json=ReportName.json
. If you specify a relative path it will be relative to the Saved/LintReports/
folder. You can also provide an absolute path to write the .json
report to.
HTML Report
To generate a .html
report, you can add the switch -html
to generate a .html
report in your project's Saved/LintReports/
folder. You can override the name of the report via -html=ReportName.html
. If you specify a relative path it will be relative to the Saved/LintReports/
folder. You can also provide an absolute path to write the .html
report to.
TreatWarningsAsErrors
If you use the -TreatWarningsAsErrors
switch, Linter will return an error code of 2 if the report contains any warnings. By default, Linter only returns an error code if it fails to lint or if the lint report contains errors.