Add Naming Convention Plugin

This commit is contained in:
Maxime Maurin 2023-09-16 22:25:51 +02:00
parent 2543e7953f
commit edb2daa78b
21 changed files with 1330 additions and 31 deletions

View File

@ -9,3 +9,22 @@ bBlueprintIsNotBlueprintType= true
[/Script/AdvancedPreviewScene.SharedProfiles]
[/Script/NamingConventionValidation.NamingConventionValidationSettings]
+ExcludedDirectories=(Path="/Engine/")
bLogWarningWhenNoClassDescriptionForAsset=False
bAllowValidationInDevelopersFolder=False
bAllowValidationOnlyInGameFolder=True
bDoesValidateOnSave=True
+ClassDescriptions=(ClassPath="/Script/Engine.AnimMontage",Prefix="AM_",Suffix="",Priority=0)
+ClassDescriptions=(ClassPath="/Script/Engine.AnimSequence",Prefix="A_",Suffix="",Priority=0)
+ClassDescriptions=(ClassPath="/Script/EnhancedInput.InputAction",Prefix="IA_",Suffix="",Priority=0)
+ClassDescriptions=(ClassPath="/Script/EnhancedInput.InputMappingContext",Prefix="IMC_",Suffix="",Priority=0)
+ClassDescriptions=(ClassPath="/Script/Engine.Material",Prefix="M_",Suffix="",Priority=0)
+ClassDescriptions=(ClassPath="/Script/Engine.MaterialInstance",Prefix="MI_",Suffix="",Priority=0)
+ClassDescriptions=(ClassPath="/Script/Engine.SkeletalMesh",Prefix="SK_",Suffix="",Priority=0)
+ClassDescriptions=(ClassPath="/Script/Engine.Skeleton",Prefix="SKEL_",Suffix="",Priority=0)
+ClassDescriptions=(ClassPath="/Script/Engine.StaticMesh",Prefix="S_",Suffix="",Priority=0)
+ClassDescriptions=(ClassPath="/Script/Engine.Texture2D",Prefix="T_",Suffix="",Priority=0)
+ClassDescriptions=(ClassPath="/Script/UMGEditor.WidgetBlueprint",Prefix="WBP_",Suffix="",Priority=0)
BlueprintsPrefix=BP_

View File

@ -1,33 +1,37 @@
{
"FileVersion": 3,
"EngineAssociation": "5.2",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "Pawn",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"CoreUObject"
]
},
{
"Name": "PawnEditor",
"Type": "Editor",
"LoadingPhase": "PostEngineInit",
"AdditionalDependencies": [
"Engine"
]
}
],
"Plugins": [
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
"TargetAllowList": [
"Editor"
]
}
]
"FileVersion": 3,
"EngineAssociation": "5.2",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "Pawn",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"CoreUObject"
]
},
{
"Name": "PawnEditor",
"Type": "Editor",
"LoadingPhase": "PostEngineInit",
"AdditionalDependencies": [
"Engine"
]
}
],
"Plugins": [
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
"TargetAllowList": [
"Editor"
]
},
{
"Name": "NamingConventionValidation",
"Enabled": true
}
]
}

View File

@ -0,0 +1,77 @@
# Visual Studio 2015 user specific files
.vs/
# Visual Studio 2015 database file
*.VC.db
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
*.ipa
# These project files can be generated by the engine
*.xcodeproj
*.xcworkspace
*.sln
*.suo
*.opensdf
*.sdf
*.VC.db
*.VC.opendb
# Precompiled Assets
SourceArt/**/*.png
SourceArt/**/*.tga
# Binary Files
Binaries/*
Plugins/*/Binaries/*
# Builds
Build/*
# Whitelist PakBlacklist-<BuildConfiguration>.txt files
!Build/*/
Build/*/**
!Build/*/PakBlacklist*.txt
# Don't ignore icon files in Build
!Build/**/*.ico
# Built data for maps
*_BuiltData.uasset
# Configuration files generated by the Editor
Saved/*
# Compiled source files for the engine to use
Intermediate/*
Plugins/*/Intermediate/*
# Cache files for the editor to use
DerivedDataCache/*

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Michael Delva
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,22 @@
{
"FileVersion" : 3,
"Version": 1.1,
"VersionName": "1.1",
"FriendlyName" : "Naming Convention Validation",
"Description" : "An asset naming convention validation plug-in.",
"Category" : "Validation",
"CreatedBy" : "Michael Delva",
"CreatedByURL": "http://www.emidee.net",
"EnabledByDefault" : false,
"CanContainContent": false,
"Modules" :
[
{
"Name" : "NamingConventionValidation",
"Type" : "Editor",
"LoadingPhase" : "PreDefault"
}
]
}

View File

@ -0,0 +1,9 @@
# UE4 Naming Convention Validation
This plug-in allows you to make sure all assets of your project are correcly named, based on your own rules.
You can validate individual asset names by defining a prefix and / or a suffix based on the asset type.
But you can also implement more complicated validation rules, like making sure all assets inside a folder have a keyword in their name.
Check the [documentation](https://theemidee.github.io/UENamingConventionValidation/) for all the informations.

View File

@ -0,0 +1,36 @@
namespace UnrealBuildTool.Rules
{
public class NamingConventionValidation : ModuleRules
{
public NamingConventionValidation( ReadOnlyTargetRules Target )
: base( Target )
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PrivateIncludePaths.Add("NamingConventionValidation/Private");
PublicDependencyModuleNames.AddRange(
new string[] {
"Core",
"CoreUObject",
"Engine",
"TargetPlatform",
"AssetRegistry",
"EditorSubsystem",
"DeveloperSettings"
}
);
PrivateDependencyModuleNames.AddRange(
new string[] {
"Slate",
"SlateCore",
"UnrealEd",
"AssetRegistry",
"EditorStyle",
"Blutility"
}
);
}
}
}

View File

@ -0,0 +1,21 @@
#include "EditorNamingValidatorBase.h"
UEditorNamingValidatorBase::UEditorNamingValidatorBase()
{
ItIsEnabled = true;
}
bool UEditorNamingValidatorBase::CanValidateAssetNaming_Implementation( const UClass * /*asset_class*/, const FAssetData & /*asset_data*/ ) const
{
return false;
}
ENamingConventionValidationResult UEditorNamingValidatorBase::ValidateAssetNaming_Implementation( FText & /*error_message*/, const UClass * /*asset_class*/, const FAssetData & /*asset_data*/ )
{
return ENamingConventionValidationResult::Unknown;
}
bool UEditorNamingValidatorBase::IsEnabled() const
{
return ItIsEnabled;
}

View File

@ -0,0 +1,472 @@
#include "EditorNamingValidatorSubsystem.h"
#include "NamingConventionValidationSettings.h"
#include <AssetRegistry/AssetRegistryModule.h>
#include <Editor.h>
#include <EditorNamingValidatorBase.h>
#include <EditorUtilityBlueprint.h>
#include <Logging/MessageLog.h>
#include <MessageLog/Public/MessageLogInitializationOptions.h>
#include <MessageLog/Public/MessageLogModule.h>
#include <Misc/ScopedSlowTask.h>
#include <UObject/UObjectHash.h>
#define LOCTEXT_NAMESPACE "NamingConventionValidationManager"
bool TryGetAssetDataRealClass( FName & asset_class, const FAssetData & asset_data )
{
static const FName
NativeParentClassKey( "NativeParentClass" ),
NativeClassKey( "NativeClass" );
if ( !asset_data.GetTagValue( NativeParentClassKey, asset_class ) )
{
if ( !asset_data.GetTagValue( NativeClassKey, asset_class ) )
{
if ( const auto * asset = asset_data.GetAsset() )
{
const FSoftClassPath class_path( asset->GetClass() );
asset_class = *class_path.ToString();
}
else
{
return false;
}
}
}
return true;
}
UEditorNamingValidatorSubsystem::UEditorNamingValidatorSubsystem()
{
AllowBlueprintValidators = true;
}
void UEditorNamingValidatorSubsystem::Initialize( FSubsystemCollectionBase & /*collection*/ )
{
const auto & asset_registry_module = FModuleManager::LoadModuleChecked< FAssetRegistryModule >( TEXT( "AssetRegistry" ) );
if ( !asset_registry_module.Get().IsLoadingAssets() )
{
RegisterBlueprintValidators();
}
else
{
if ( !asset_registry_module.Get().OnFilesLoaded().IsBoundToObject( this ) )
{
asset_registry_module.Get().OnFilesLoaded().AddUObject( this, &UEditorNamingValidatorSubsystem::RegisterBlueprintValidators );
}
}
TArray< UClass * > validator_classes;
GetDerivedClasses( UEditorNamingValidatorBase::StaticClass(), validator_classes );
for ( const auto * validator_class : validator_classes )
{
if ( !validator_class->HasAllClassFlags( CLASS_Abstract ) )
{
if ( const auto * class_package = validator_class->GetOuterUPackage() )
{
const auto module_name = FPackageName::GetShortFName( class_package->GetFName() );
if ( FModuleManager::Get().IsModuleLoaded( module_name ) )
{
auto * validator = NewObject< UEditorNamingValidatorBase >( GetTransientPackage(), validator_class );
AddValidator( validator );
}
}
}
}
FMessageLogInitializationOptions init_options;
init_options.bShowFilters = true;
auto & message_log_module = FModuleManager::LoadModuleChecked< FMessageLogModule >( "MessageLog" );
message_log_module.RegisterLogListing( "NamingConventionValidation", LOCTEXT( "NamingConventionValidation", "Naming Convention Validation" ), init_options );
auto * settings = GetMutableDefault< UNamingConventionValidationSettings >();
settings->PostProcessSettings();
}
void UEditorNamingValidatorSubsystem::Deinitialize()
{
CleanupValidators();
Super::Deinitialize();
}
int32 UEditorNamingValidatorSubsystem::ValidateAssets( const TArray< FAssetData > & asset_data_list, bool /*skip_excluded_directories*/, const bool show_if_no_failures ) const
{
const auto * settings = GetDefault< UNamingConventionValidationSettings >();
FScopedSlowTask slow_task( 1.0f, LOCTEXT( "NamingConventionValidatingDataTask", "Validating Naming Convention..." ) );
slow_task.Visibility = show_if_no_failures ? ESlowTaskVisibility::ForceVisible : ESlowTaskVisibility::Invisible;
if ( show_if_no_failures )
{
slow_task.MakeDialogDelayed( 0.1f );
}
FMessageLog data_validation_log( "NamingConventionValidation" );
auto num_files_checked = 0;
auto num_valid_files = 0;
auto num_invalid_files = 0;
auto num_files_skipped = 0;
auto num_files_unable_to_validate = 0;
const auto num_files_to_validate = asset_data_list.Num();
for ( const auto & asset_data : asset_data_list )
{
slow_task.EnterProgressFrame( 1.0f / num_files_to_validate, FText::Format( LOCTEXT( "ValidatingNamingConventionFilename", "Validating Naming Convention {0}" ), FText::FromString( asset_data.GetFullName() ) ) );
FText error_message;
const auto result = IsAssetNamedCorrectly( error_message, asset_data );
switch ( result )
{
case ENamingConventionValidationResult::Excluded:
{
data_validation_log.Info()
->AddToken( FAssetNameToken::Create( asset_data.PackageName.ToString() ) )
->AddToken( FTextToken::Create( LOCTEXT( "ExcludedNamingConventionResult", "has not been tested based on the configuration." ) ) )
->AddToken( FTextToken::Create( error_message ) );
++num_files_skipped;
}
break;
case ENamingConventionValidationResult::Valid:
{
++num_valid_files;
++num_files_checked;
}
break;
case ENamingConventionValidationResult::Invalid:
{
data_validation_log.Error()
->AddToken( FAssetNameToken::Create( asset_data.PackageName.ToString() ) )
->AddToken( FTextToken::Create( LOCTEXT( "InvalidNamingConventionResult", "does not match naming convention." ) ) )
->AddToken( FTextToken::Create( error_message ) );
++num_invalid_files;
++num_files_checked;
}
break;
case ENamingConventionValidationResult::Unknown:
{
if ( show_if_no_failures && settings->bLogWarningWhenNoClassDescriptionForAsset )
{
FFormatNamedArguments arguments;
arguments.Add( TEXT( "ClassName" ), FText::FromString( asset_data.AssetClassPath.ToString() ) );
data_validation_log.Warning()
->AddToken( FAssetNameToken::Create( asset_data.PackageName.ToString() ) )
->AddToken( FTextToken::Create( LOCTEXT( "UnknownNamingConventionResult", "has no known naming convention." ) ) )
->AddToken( FTextToken::Create( FText::Format( LOCTEXT( "UnknownClass", " Class = {ClassName}" ), arguments ) ) );
}
++num_files_checked;
++num_files_unable_to_validate;
}
break;
}
}
const auto has_failed = num_invalid_files > 0;
if ( has_failed || show_if_no_failures )
{
FFormatNamedArguments arguments;
arguments.Add( TEXT( "Result" ), has_failed ? LOCTEXT( "Failed", "FAILED" ) : LOCTEXT( "Succeeded", "SUCCEEDED" ) );
arguments.Add( TEXT( "NumChecked" ), num_files_checked );
arguments.Add( TEXT( "NumValid" ), num_valid_files );
arguments.Add( TEXT( "NumInvalid" ), num_invalid_files );
arguments.Add( TEXT( "NumSkipped" ), num_files_skipped );
arguments.Add( TEXT( "NumUnableToValidate" ), num_files_unable_to_validate );
auto validation_log = has_failed ? data_validation_log.Error() : data_validation_log.Info();
validation_log->AddToken( FTextToken::Create( FText::Format( LOCTEXT( "SuccessOrFailure", "NamingConvention Validation {Result}." ), arguments ) ) );
validation_log->AddToken( FTextToken::Create( FText::Format( LOCTEXT( "ResultsSummary", "Files Checked: {NumChecked}, Passed: {NumValid}, Failed: {NumInvalid}, Skipped: {NumSkipped}, Unable to validate: {NumUnableToValidate}" ), arguments ) ) );
data_validation_log.Open( EMessageSeverity::Info, true );
}
return num_invalid_files;
}
void UEditorNamingValidatorSubsystem::ValidateSavedPackage( const FName package_name )
{
auto * settings = GetDefault< UNamingConventionValidationSettings >();
if ( !settings->bDoesValidateOnSave || GEditor->IsAutosaving() )
{
return;
}
SavedPackagesToValidate.AddUnique( package_name );
GEditor->GetTimerManager()->SetTimerForNextTick( this, &UEditorNamingValidatorSubsystem::ValidateAllSavedPackages );
}
void UEditorNamingValidatorSubsystem::AddValidator( UEditorNamingValidatorBase * validator )
{
if ( validator )
{
Validators.Add( validator->GetClass(), validator );
}
}
ENamingConventionValidationResult UEditorNamingValidatorSubsystem::IsAssetNamedCorrectly( FText & error_message, const FAssetData & asset_data, bool can_use_editor_validators ) const
{
const auto * settings = GetDefault< UNamingConventionValidationSettings >();
if ( settings->IsPathExcludedFromValidation( asset_data.PackageName.ToString() ) )
{
error_message = LOCTEXT( "ExcludedFolder", "The asset is in an excluded directory" );
return ENamingConventionValidationResult::Excluded;
}
FName asset_class;
if ( !TryGetAssetDataRealClass( asset_class, asset_data ) )
{
error_message = LOCTEXT( "UnknownClass", "The asset is of a class which has not been set up in the settings" );
return ENamingConventionValidationResult::Unknown;
}
return DoesAssetMatchNameConvention( error_message, asset_data, asset_class, can_use_editor_validators );
}
void UEditorNamingValidatorSubsystem::RegisterBlueprintValidators()
{
if ( !AllowBlueprintValidators )
{
return;
}
// Locate all validators (include unloaded)
const auto & asset_registry_module = FModuleManager::LoadModuleChecked< FAssetRegistryModule >( TEXT( "AssetRegistry" ) );
TArray< FAssetData > all_blueprint_asset_data;
asset_registry_module.Get().GetAssetsByClass( UEditorUtilityBlueprint::StaticClass()->GetClassPathName(), all_blueprint_asset_data, true );
for ( auto & asset_data : all_blueprint_asset_data )
{
FString parent_class_name;
if ( !asset_data.GetTagValue( FBlueprintTags::NativeParentClassPath, parent_class_name ) )
{
asset_data.GetTagValue( FBlueprintTags::ParentClassPath, parent_class_name );
}
if ( !parent_class_name.IsEmpty() )
{
const UClass* parent_class = nullptr;
UObject* Outer = nullptr;
ResolveName(Outer, parent_class_name, false, false);
parent_class = FindObject<UClass>(Outer, *parent_class_name);
if ( parent_class == nullptr
|| !parent_class->IsChildOf( UEditorNamingValidatorBase::StaticClass() ) )
{
continue;
}
}
// If this object isn't currently loaded, load it
auto * validator_object = asset_data.ToSoftObjectPath().ResolveObject();
if ( validator_object == nullptr )
{
validator_object = asset_data.ToSoftObjectPath().TryLoad();
}
if ( validator_object )
{
const auto * validator_blueprint = Cast< UEditorUtilityBlueprint >( validator_object );
auto * validator = NewObject< UEditorNamingValidatorBase >( GetTransientPackage(), validator_blueprint->GeneratedClass );
AddValidator( validator );
}
}
}
void UEditorNamingValidatorSubsystem::CleanupValidators()
{
Validators.Empty();
}
void UEditorNamingValidatorSubsystem::ValidateAllSavedPackages()
{
const auto & asset_registry_module = FModuleManager::LoadModuleChecked< FAssetRegistryModule >( "AssetRegistry" );
TArray< FAssetData > assets;
for ( const auto package_name : SavedPackagesToValidate )
{
// We need to query the in-memory data as the disk cache may not be accurate
asset_registry_module.Get().GetAssetsByPackageName( package_name, assets );
}
ValidateOnSave( assets );
SavedPackagesToValidate.Empty();
}
void UEditorNamingValidatorSubsystem::ValidateOnSave( const TArray< FAssetData > & asset_data_list ) const
{
const auto * settings = GetDefault< UNamingConventionValidationSettings >();
if ( !settings->bDoesValidateOnSave || GEditor->IsAutosaving() )
{
return;
}
FMessageLog data_validation_log( "NamingConventionValidation" );
if ( ValidateAssets( asset_data_list, true, false ) > 0 )
{
const auto error_message_notification = FText::Format(
LOCTEXT( "ValidationFailureNotification", "Naming Convention Validation failed when saving {0}, check Naming Convention Validation log" ),
asset_data_list.Num() == 1 ? FText::FromName( asset_data_list[ 0 ].AssetName ) : LOCTEXT( "MultipleErrors", "multiple assets" ) );
data_validation_log.Notify( error_message_notification, EMessageSeverity::Warning, /*bForce=*/true );
}
}
ENamingConventionValidationResult UEditorNamingValidatorSubsystem::DoesAssetMatchNameConvention( FText & error_message, const FAssetData & asset_data, const FName asset_class, bool can_use_editor_validators ) const
{
const auto * settings = GetDefault< UNamingConventionValidationSettings >();
static const FTopLevelAssetPath BlueprintGeneratedClassName( FName( TEXT( "/" ) ), FName( TEXT( "BlueprintGeneratedClass" ) ) );
auto asset_name = asset_data.AssetName.ToString();
// Starting UE4.27 (?) some blueprints now have BlueprintGeneratedClass as their AssetClass, and their name ends with a _C.
if ( asset_data.AssetClassPath == BlueprintGeneratedClassName )
{
asset_name.RemoveFromEnd( TEXT( "_C" ), ESearchCase::CaseSensitive );
}
const FSoftClassPath asset_class_path( asset_class.ToString() );
if ( const auto * asset_real_class = asset_class_path.TryLoadClass< UObject >() )
{
if ( IsClassExcluded( error_message, asset_real_class ) )
{
return ENamingConventionValidationResult::Excluded;
}
auto result = ENamingConventionValidationResult::Unknown;
if ( can_use_editor_validators )
{
result = DoesAssetMatchesValidators( error_message, asset_real_class, asset_data );
if ( result != ENamingConventionValidationResult::Unknown )
{
return result;
}
}
result = DoesAssetMatchesClassDescriptions( error_message, asset_real_class, asset_name );
if ( result != ENamingConventionValidationResult::Unknown )
{
return result;
}
}
static const FTopLevelAssetPath BlueprintClassName( FName( TEXT( "/Script/Engine" ) ), FName( TEXT( "Blueprint" ) ) );
if ( asset_data.AssetClassPath == BlueprintClassName || asset_data.AssetClassPath == BlueprintGeneratedClassName )
{
if ( !asset_name.StartsWith( settings->BlueprintsPrefix ) )
{
error_message = FText::FromString( TEXT( "Generic blueprint assets must start with BP_" ) );
return ENamingConventionValidationResult::Invalid;
}
return ENamingConventionValidationResult::Valid;
}
return ENamingConventionValidationResult::Unknown;
}
bool UEditorNamingValidatorSubsystem::IsClassExcluded( FText & error_message, const UClass * asset_class ) const
{
const auto * settings = GetDefault< UNamingConventionValidationSettings >();
for ( const auto * excluded_class : settings->ExcludedClasses )
{
if ( asset_class->IsChildOf( excluded_class ) )
{
error_message = FText::Format( LOCTEXT( "ExcludedClass", "Assets of class '{0}' are excluded from naming convention validation" ), FText::FromString( excluded_class->GetDefaultObjectName().ToString() ) );
return true;
}
}
return false;
}
ENamingConventionValidationResult UEditorNamingValidatorSubsystem::DoesAssetMatchesClassDescriptions( FText & error_message, const UClass * asset_class, const FString & asset_name ) const
{
const auto * settings = GetDefault< UNamingConventionValidationSettings >();
const UClass* MostPreciseClass = UObject::StaticClass();
ENamingConventionValidationResult Result = ENamingConventionValidationResult::Unknown;
for ( const auto & class_description : settings->ClassDescriptions )
{
if ( class_description.Class == nullptr )
{
FMessageLog data_validation_log( "NamingConventionValidation" );
data_validation_log.Warning()
->AddToken( FTextToken::Create( LOCTEXT( "InvalidClassDescription", "invalid class description found." ) ) );
continue;
}
const bool bClassFilterMatches = asset_class->IsChildOf( class_description.Class );
const bool bClassIsMoreOrSamePrecise = class_description.Class->IsChildOf(MostPreciseClass);
const bool bClassIsSamePrecise = bClassIsMoreOrSamePrecise && class_description.Class == MostPreciseClass;
const bool bClassIsMorePrecise = bClassIsMoreOrSamePrecise && class_description.Class != MostPreciseClass;
// had an error on this precision level before. but there could be another filter that passes
const bool bSamePrecisionCanBeValid = bClassIsSamePrecise && Result != ENamingConventionValidationResult::Valid;
const bool bCheckAffixes = bClassFilterMatches && (bClassIsMorePrecise || bSamePrecisionCanBeValid);
if ( bCheckAffixes )
{
MostPreciseClass = class_description.Class;
error_message = FText::GetEmpty();
Result = ENamingConventionValidationResult::Valid;
if ( !class_description.Prefix.IsEmpty() )
{
if ( !asset_name.StartsWith( class_description.Prefix ) )
{
error_message = FText::Format( LOCTEXT( "WrongPrefix", "Assets of class '{0}' must have a name which starts with {1}" ), FText::FromString( class_description.ClassPath.ToString() ), FText::FromString( class_description.Prefix ) );
Result = ENamingConventionValidationResult::Invalid;
}
}
if ( !class_description.Suffix.IsEmpty() )
{
if ( !asset_name.EndsWith( class_description.Suffix ) )
{
error_message = FText::Format( LOCTEXT( "WrongSuffix", "Assets of class '{0}' must have a name which ends with {1}" ), FText::FromString( class_description.ClassPath.ToString() ), FText::FromString( class_description.Suffix ) );
Result = ENamingConventionValidationResult::Invalid;
}
}
}
}
return Result;
}
ENamingConventionValidationResult UEditorNamingValidatorSubsystem::DoesAssetMatchesValidators( FText & error_message, const UClass * asset_class, const FAssetData & asset_data ) const
{
for ( const auto & validator_pair : Validators )
{
if ( validator_pair.Value != nullptr && validator_pair.Value->IsEnabled() && validator_pair.Value->CanValidateAssetNaming( asset_class, asset_data ) )
{
const auto result = validator_pair.Value->ValidateAssetNaming( error_message, asset_class, asset_data );
if ( result != ENamingConventionValidationResult::Valid )
{
return result;
}
}
}
return ENamingConventionValidationResult::Unknown;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,56 @@
#include "NamingConventionValidationCommandlet.h"
#include "NamingConventionValidationLog.h"
#include "EditorNamingValidatorSubsystem.h"
#include <Editor.h>
#include <AssetRegistry/AssetRegistryHelpers.h>
#include <AssetRegistry/AssetRegistryModule.h>
#include <AssetRegistry/IAssetRegistry.h>
UNamingConventionValidationCommandlet::UNamingConventionValidationCommandlet()
{
LogToConsole = false;
}
int32 UNamingConventionValidationCommandlet::Main( const FString & params )
{
UE_LOG( LogNamingConventionValidation, Log, TEXT( "--------------------------------------------------------------------------------------------" ) );
UE_LOG( LogNamingConventionValidation, Log, TEXT( "Running NamingConventionValidation Commandlet" ) );
TArray< FString > tokens;
TArray< FString > switches;
TMap< FString, FString > params_map;
ParseCommandLine( *params, tokens, switches, params_map );
// validate data
if ( !ValidateData() )
{
UE_LOG( LogNamingConventionValidation, Warning, TEXT( "Errors occurred while validating naming convention" ) );
return 2; // return something other than 1 for error since the engine will return 1 if any other system (possibly unrelated) logged errors during execution.
}
UE_LOG( LogNamingConventionValidation, Log, TEXT( "Successfully finished running NamingConventionValidation Commandlet" ) );
UE_LOG( LogNamingConventionValidation, Log, TEXT( "--------------------------------------------------------------------------------------------" ) );
return 0;
}
//static
bool UNamingConventionValidationCommandlet::ValidateData()
{
auto & asset_registry_module = FModuleManager::LoadModuleChecked< FAssetRegistryModule >( AssetRegistryConstants::ModuleName );
TArray< FAssetData > asset_data_list;
FARFilter filter;
filter.bRecursivePaths = true;
filter.PackagePaths.Add( "/Game" );
asset_registry_module.Get().GetAssets( filter, asset_data_list );
auto * editor_validator_subsystem = GEditor->GetEditorSubsystem< UEditorNamingValidatorSubsystem >();
check( editor_validator_subsystem );
// ReSharper disable once CppExpressionWithoutSideEffects
editor_validator_subsystem->ValidateAssets( asset_data_list );
return true;
}

View File

@ -0,0 +1,3 @@
#include "NamingConventionValidationLog.h"
DEFINE_LOG_CATEGORY( LogNamingConventionValidation );

View File

@ -0,0 +1,237 @@
#include "NamingConventionValidationModule.h"
#include "EditorNamingValidatorSubsystem.h"
#include "NamingConventionValidationCommandlet.h"
#include <AssetRegistry/AssetRegistryModule.h>
#include <AssetToolsModule.h>
#include <ContentBrowserDelegates.h>
#include <ContentBrowserModule.h>
#include <EditorStyleSet.h>
#include <Framework/Application/SlateApplication.h>
#include <Framework/MultiBox/MultiBoxBuilder.h>
#include <Framework/MultiBox/MultiBoxExtender.h>
#include <LevelEditor.h>
#include <Misc/MessageDialog.h>
#include <Modules/ModuleManager.h>
#include <UObject/Object.h>
#include <UObject/ObjectSaveContext.h>
#define LOCTEXT_NAMESPACE "NamingConventionValidationModule"
void FindAssetDependencies( const FAssetRegistryModule & asset_registry_module, const FAssetData & asset_data, TSet< FAssetData > & dependent_assets )
{
if ( asset_data.IsValid() )
{
if ( const auto object = asset_data.GetAsset() )
{
const auto selected_package_name = object->GetOutermost()->GetFName();
const auto package_string = selected_package_name.ToString();
if ( !dependent_assets.Contains( asset_data ) )
{
dependent_assets.Add( asset_data );
TArray< FName > dependencies;
asset_registry_module.Get().GetDependencies( selected_package_name, dependencies, UE::AssetRegistry::EDependencyCategory::Package );
for ( const auto dependency : dependencies )
{
const auto dependency_package_string = dependency.ToString();
auto dependency_object_string = FString::Printf( TEXT( "%s.%s" ), *dependency_package_string, *FPackageName::GetLongPackageAssetName( dependency_package_string ) );
// recurse on each dependency
const FSoftObjectPath object_path( *dependency_object_string );
auto dependent_asset = asset_registry_module.Get().GetAssetByObjectPath( object_path );
FindAssetDependencies( asset_registry_module, dependent_asset, dependent_assets );
}
}
}
}
}
void OnPackageSaved( const FString & /*package_file_name*/, UPackage* package, FObjectPostSaveContext context )
{
if ( auto * editor_validation_subsystem = GEditor->GetEditorSubsystem< UEditorNamingValidatorSubsystem >() )
{
editor_validation_subsystem->ValidateSavedPackage( package->GetFName() );
}
}
void ValidateAssets( const TArray< FAssetData > selected_assets )
{
if ( auto * editor_validation_subsystem = GEditor->GetEditorSubsystem< UEditorNamingValidatorSubsystem >() )
{
editor_validation_subsystem->ValidateAssets( selected_assets );
}
}
void ValidateFolders( const TArray< FString > selected_folders )
{
auto & asset_registry_module = FModuleManager::Get().LoadModuleChecked< FAssetRegistryModule >( TEXT( "AssetRegistry" ) );
FARFilter filter;
filter.bRecursivePaths = true;
for ( const auto & folder : selected_folders )
{
filter.PackagePaths.Emplace( *folder );
}
TArray< FAssetData > asset_list;
asset_registry_module.Get().GetAssets( filter, asset_list );
ValidateAssets( asset_list );
}
void CreateDataValidationContentBrowserAssetMenu( FMenuBuilder & menu_builder, const TArray< FAssetData > selected_assets )
{
menu_builder.AddMenuSeparator();
menu_builder.AddMenuEntry(
LOCTEXT( "NamingConventionValidateAssetsTabTitle", "Validate Assets Naming Convention" ),
LOCTEXT( "NamingConventionValidateAssetsTooltipText", "Run naming convention validation on these assets." ),
FSlateIcon(),
FUIAction( FExecuteAction::CreateStatic( ValidateAssets, selected_assets ) ) );
}
TSharedRef< FExtender > OnExtendContentBrowserAssetSelectionMenu( const TArray< FAssetData > & selected_assets )
{
TSharedRef< FExtender > extender( new FExtender() );
extender->AddMenuExtension(
"AssetContextAdvancedActions",
EExtensionHook::After,
nullptr,
FMenuExtensionDelegate::CreateStatic( CreateDataValidationContentBrowserAssetMenu, selected_assets ) );
return extender;
}
void CreateDataValidationContentBrowserPathMenu( FMenuBuilder & menu_builder, const TArray< FString > selected_paths )
{
menu_builder.AddMenuEntry(
LOCTEXT( "NamingConventionValidateAssetsPathTabTitle", "Validate Assets Naming Convention in Folder" ),
LOCTEXT( "NamingConventionValidateAssetsPathTooltipText", "Runs naming convention validation on the assets in the selected folder." ),
FSlateIcon(),
FUIAction( FExecuteAction::CreateStatic( ValidateFolders, selected_paths ) ) );
}
TSharedRef< FExtender > OnExtendContentBrowserPathSelectionMenu( const TArray< FString > & selected_paths )
{
TSharedRef< FExtender > extender( new FExtender() );
extender->AddMenuExtension(
"PathContextBulkOperations",
EExtensionHook::After,
nullptr,
FMenuExtensionDelegate::CreateStatic( CreateDataValidationContentBrowserPathMenu, selected_paths ) );
return extender;
}
FText MenuValidateDataGetTitle()
{
auto & asset_registry_module = FModuleManager::LoadModuleChecked< FAssetRegistryModule >( "AssetRegistry" );
if ( asset_registry_module.Get().IsLoadingAssets() )
{
return LOCTEXT( "NamingConventionValidationTitleDA", "Validate Naming Convention [Discovering Assets]" );
}
return LOCTEXT( "NamingConventionValidationTitle", "Naming Convention..." );
}
void MenuValidateData()
{
// make sure the asset registry is finished building
auto & asset_registry_module = FModuleManager::LoadModuleChecked< FAssetRegistryModule >( "AssetRegistry" );
if ( asset_registry_module.Get().IsLoadingAssets() )
{
FMessageDialog::Open( EAppMsgType::Ok, LOCTEXT( "AssetsStillScanningError", "Cannot run naming convention validation while still discovering assets." ) );
return;
}
const auto success = UNamingConventionValidationCommandlet::ValidateData();
if ( !success )
{
FMessageDialog::Open( EAppMsgType::Ok, LOCTEXT( "NamingConventionValidationError", "An error was encountered during naming convention validation. See the log for details." ) );
}
}
void NamingConventionValidationMenuCreationDelegate( FMenuBuilder & menu_builder )
{
menu_builder.BeginSection( "NamingConventionValidation", LOCTEXT( "NamingConventionValidation", "NamingConventionValidation" ) );
menu_builder.AddMenuEntry(
TAttribute< FText >::Create( &MenuValidateDataGetTitle ),
LOCTEXT( "NamingConventionValidationTooltip", "Validates all naming convention in content directory." ),
FSlateIcon( FAppStyle::GetAppStyleSetName(), "DeveloperTools.MenuIcon" ),
FUIAction( FExecuteAction::CreateStatic( &MenuValidateData ) ) );
menu_builder.EndSection();
}
class FNamingConventionValidationModule : public INamingConventionValidationModule
{
public:
void StartupModule() override;
void ShutdownModule() override;
private:
TSharedPtr< FExtender > MenuExtender;
FDelegateHandle ContentBrowserAssetExtenderDelegateHandle;
FDelegateHandle ContentBrowserPathExtenderDelegateHandle;
FDelegateHandle OnPackageSavedDelegateHandle;
};
IMPLEMENT_MODULE( FNamingConventionValidationModule, NamingConventionValidation )
void FNamingConventionValidationModule::StartupModule()
{
if ( !IsRunningCommandlet() && !IsRunningGame() && FSlateApplication::IsInitialized() )
{
auto & content_browser_module = FModuleManager::LoadModuleChecked< FContentBrowserModule >( TEXT( "ContentBrowser" ) );
auto & cb_asset_menu_extender_delegates = content_browser_module.GetAllAssetViewContextMenuExtenders();
cb_asset_menu_extender_delegates.Add( FContentBrowserMenuExtender_SelectedAssets::CreateStatic( OnExtendContentBrowserAssetSelectionMenu ) );
ContentBrowserAssetExtenderDelegateHandle = cb_asset_menu_extender_delegates.Last().GetHandle();
auto & cb_folder_menu_extender_delegates = content_browser_module.GetAllPathViewContextMenuExtenders();
cb_folder_menu_extender_delegates.Add( FContentBrowserMenuExtender_SelectedPaths::CreateStatic( OnExtendContentBrowserPathSelectionMenu ) );
ContentBrowserPathExtenderDelegateHandle = cb_folder_menu_extender_delegates.Last().GetHandle();
MenuExtender = MakeShareable( new FExtender );
MenuExtender->AddMenuExtension( "FileLoadAndSave", EExtensionHook::After, nullptr, FMenuExtensionDelegate::CreateStatic( NamingConventionValidationMenuCreationDelegate ) );
auto & level_editor_module = FModuleManager::LoadModuleChecked< FLevelEditorModule >( "LevelEditor" );
level_editor_module.GetMenuExtensibilityManager()->AddExtender( MenuExtender );
OnPackageSavedDelegateHandle = UPackage::PackageSavedWithContextEvent.AddStatic( OnPackageSaved );
}
}
void FNamingConventionValidationModule::ShutdownModule()
{
if ( !IsRunningCommandlet() && !IsRunningGame() && !IsRunningDedicatedServer() )
{
if ( auto * content_browser_module = FModuleManager::GetModulePtr< FContentBrowserModule >( TEXT( "ContentBrowser" ) ) )
{
auto & content_browser_menu_extender_delegates = content_browser_module->GetAllAssetViewContextMenuExtenders();
content_browser_menu_extender_delegates.RemoveAll( [ &extender_delegate = ContentBrowserAssetExtenderDelegateHandle ]( const FContentBrowserMenuExtender_SelectedAssets & delegate ) {
return delegate.GetHandle() == extender_delegate;
} );
content_browser_menu_extender_delegates.RemoveAll( [ &extender_delegate = ContentBrowserAssetExtenderDelegateHandle ]( const FContentBrowserMenuExtender_SelectedAssets & delegate ) {
return delegate.GetHandle() == extender_delegate;
} );
}
if ( auto * level_editor_module = FModuleManager::GetModulePtr< FLevelEditorModule >( "LevelEditor" ) )
{
level_editor_module->GetMenuExtensibilityManager()->RemoveExtender( MenuExtender );
}
MenuExtender = nullptr;
UPackage::PackageSavedWithContextEvent.Remove( OnPackageSavedDelegateHandle );
}
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,90 @@
#include "NamingConventionValidation/Public/NamingConventionValidationSettings.h"
#include "NamingConventionValidationLog.h"
UNamingConventionValidationSettings::UNamingConventionValidationSettings()
{
bLogWarningWhenNoClassDescriptionForAsset = false;
bAllowValidationInDevelopersFolder = false;
bAllowValidationOnlyInGameFolder = true;
bDoesValidateOnSave = true;
BlueprintsPrefix = "BP_";
}
bool UNamingConventionValidationSettings::IsPathExcludedFromValidation( const FString & path ) const
{
if ( !path.StartsWith( "/Game/" ) && bAllowValidationOnlyInGameFolder )
{
auto can_process_folder = NonGameFoldersDirectoriesToProcess.FindByPredicate( [ &path ]( const auto & directory ) {
return path.StartsWith( directory.Path );
} ) != nullptr;
if ( !can_process_folder )
{
can_process_folder = NonGameFoldersDirectoriesToProcessContainingToken.FindByPredicate( [ &path ]( const auto & token ) {
return path.Contains( token );
} ) != nullptr;
}
if ( !can_process_folder )
{
return true;
}
}
if ( path.StartsWith( "/Game/Developers/" ) && !bAllowValidationInDevelopersFolder )
{
return true;
}
for ( const auto & excluded_path : ExcludedDirectories )
{
if ( path.StartsWith( excluded_path.Path ) )
{
return true;
}
}
return false;
}
void UNamingConventionValidationSettings::PostProcessSettings()
{
for ( auto & class_description : ClassDescriptions )
{
class_description.Class = class_description.ClassPath.LoadSynchronous();
UE_CLOG( class_description.Class == nullptr, LogNamingConventionValidation, Warning, TEXT( "Impossible to get a valid UClass for the classpath %s" ), *class_description.ClassPath.ToString() );
}
ClassDescriptions.Sort();
for ( auto & class_path : ExcludedClassPaths )
{
auto * excluded_class = class_path.LoadSynchronous();
UE_CLOG( excluded_class == nullptr, LogNamingConventionValidation, Warning, TEXT( "Impossible to get a valid UClass for the excluded classpath %s" ), *class_path.ToString() );
if ( excluded_class != nullptr )
{
ExcludedClasses.Add( excluded_class );
}
}
static const FDirectoryPath
EngineDirectoryPath( { TEXT( "/Engine/" ) } );
// Cannot use AddUnique since FDirectoryPath does not have operator==
if ( !ExcludedDirectories.ContainsByPredicate( []( const auto & item ) {
return item.Path == EngineDirectoryPath.Path;
} ) )
{
ExcludedDirectories.Add( EngineDirectoryPath );
}
}
#if WITH_EDITOR
void UNamingConventionValidationSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
PostProcessSettings();
}
#endif

View File

@ -0,0 +1,29 @@
#pragma once
#include "NamingConventionValidationTypes.h"
#include <CoreMinimal.h>
#include <UObject/NoExportTypes.h>
#include "EditorNamingValidatorBase.generated.h"
UCLASS( Abstract, Blueprintable, meta = (ShowWorldContextPin) )
class NAMINGCONVENTIONVALIDATION_API UEditorNamingValidatorBase : public UObject
{
GENERATED_BODY()
public:
UEditorNamingValidatorBase();
UFUNCTION( BlueprintNativeEvent, BlueprintPure, Category = "Asset Naming Validation" )
bool CanValidateAssetNaming( const UClass * asset_class, const FAssetData & asset_data ) const;
UFUNCTION( BlueprintNativeEvent, Category = "Asset Naming Validation" )
ENamingConventionValidationResult ValidateAssetNaming( FText & error_message, const UClass * asset_class, const FAssetData & asset_data );
virtual bool IsEnabled() const;
protected:
UPROPERTY( EditAnywhere, Category = "Asset Validation", meta = ( BlueprintProtected = true ), DisplayName = "IsEnabled" )
uint8 ItIsEnabled : 1;
};

View File

@ -0,0 +1,46 @@
#pragma once
#include "NamingConventionValidationTypes.h"
#include <CoreMinimal.h>
#include <EditorSubsystem.h>
#include "EditorNamingValidatorSubsystem.generated.h"
class UEditorNamingValidatorBase;
struct FAssetData;
UCLASS( Config = Editor )
class NAMINGCONVENTIONVALIDATION_API UEditorNamingValidatorSubsystem final : public UEditorSubsystem
{
GENERATED_BODY()
public:
UEditorNamingValidatorSubsystem();
void Initialize( FSubsystemCollectionBase & collection ) override;
void Deinitialize() override;
int32 ValidateAssets( const TArray< FAssetData > & asset_data_list, bool skip_excluded_directories = true, bool show_if_no_failures = true ) const;
void ValidateSavedPackage( FName package_name );
void AddValidator( UEditorNamingValidatorBase * validator );
ENamingConventionValidationResult IsAssetNamedCorrectly( FText & error_message, const FAssetData & asset_data, bool can_use_editor_validators = true ) const;
private:
void RegisterBlueprintValidators();
void CleanupValidators();
void ValidateAllSavedPackages();
void ValidateOnSave( const TArray< FAssetData > & asset_data_list ) const;
ENamingConventionValidationResult DoesAssetMatchNameConvention( FText & error_message, const FAssetData & asset_data, FName asset_class, bool can_use_editor_validators = true ) const;
bool IsClassExcluded( FText & error_message, const UClass * asset_class ) const;
ENamingConventionValidationResult DoesAssetMatchesClassDescriptions( FText & error_message, const UClass * asset_class, const FString & asset_name ) const;
ENamingConventionValidationResult DoesAssetMatchesValidators( FText & error_message, const UClass * asset_class, const FAssetData & asset_data ) const;
UPROPERTY( config )
uint8 AllowBlueprintValidators : 1;
UPROPERTY( Transient )
TMap< UClass *, UEditorNamingValidatorBase * > Validators;
TArray< FName > SavedPackagesToValidate;
};

View File

@ -0,0 +1,21 @@
#pragma once
#include <Commandlets/Commandlet.h>
#include "NamingConventionValidationCommandlet.generated.h"
UCLASS( CustomConstructor )
class NAMINGCONVENTIONVALIDATION_API UNamingConventionValidationCommandlet : public UCommandlet
{
GENERATED_BODY()
public:
UNamingConventionValidationCommandlet();
// Begin UCommandlet Interface
int32 Main( const FString & params ) override;
// End UCommandlet Interface
static bool ValidateData();
};

View File

@ -0,0 +1,5 @@
#pragma once
#include <CoreMinimal.h>
DECLARE_LOG_CATEGORY_EXTERN( LogNamingConventionValidation, Verbose, All )

View File

@ -0,0 +1,24 @@
#pragma once
#include <CoreMinimal.h>
#include <Modules/ModuleInterface.h>
#include <Modules/ModuleManager.h>
#include <Stats/Stats.h>
class NAMINGCONVENTIONVALIDATION_API INamingConventionValidationModule : public IModuleInterface
{
public:
static INamingConventionValidationModule & Get()
{
QUICK_SCOPE_CYCLE_COUNTER( STAT_INamingConventionValidationModule_Get );
static auto & singleton = FModuleManager::LoadModuleChecked< INamingConventionValidationModule >( "NamingConventionValidation" );
return singleton;
}
static bool IsAvailable()
{
QUICK_SCOPE_CYCLE_COUNTER( STAT_INamingConventionValidationModule_IsAvailable );
return FModuleManager::Get().IsModuleLoaded( "NamingConventionValidation" );
}
};

View File

@ -0,0 +1,90 @@
#pragma once
#include <CoreMinimal.h>
#include <Engine/DeveloperSettings.h>
#include <Engine/EngineTypes.h>
#include "NamingConventionValidationSettings.generated.h"
USTRUCT()
struct FNamingConventionValidationClassDescription
{
GENERATED_USTRUCT_BODY()
FNamingConventionValidationClassDescription() :
Class( nullptr ),
Priority( 0 )
{}
bool operator<( const FNamingConventionValidationClassDescription & other ) const
{
return Priority > other.Priority || ((Class && other.Class) ? (Class->GetName() < other.Class->GetName()) : false);
}
UPROPERTY( config, EditAnywhere, meta = ( AllowAbstract = true ) )
TSoftClassPtr< UObject > ClassPath;
UPROPERTY( transient )
UClass * Class;
UPROPERTY( config, EditAnywhere )
FString Prefix;
UPROPERTY( config, EditAnywhere )
FString Suffix;
UPROPERTY( config, EditAnywhere )
int Priority;
};
UCLASS( config = Editor, DefaultConfig )
class NAMINGCONVENTIONVALIDATION_API UNamingConventionValidationSettings final : public UDeveloperSettings
{
GENERATED_BODY()
public:
UNamingConventionValidationSettings();
bool IsPathExcludedFromValidation( const FString & path ) const;
UPROPERTY( config, EditAnywhere, meta = ( LongPackageName, ConfigRestartRequired = true ) )
TArray< FDirectoryPath > ExcludedDirectories;
UPROPERTY( config, EditAnywhere )
uint8 bLogWarningWhenNoClassDescriptionForAsset : 1;
UPROPERTY( config, EditAnywhere )
uint8 bAllowValidationInDevelopersFolder : 1;
UPROPERTY( config, EditAnywhere )
uint8 bAllowValidationOnlyInGameFolder : 1;
// Add folders located outside of /Game that you still want to process when bAllowValidationOnlyInGameFolder is checked
UPROPERTY( config, EditAnywhere, meta = ( LongPackageName, ConfigRestartRequired = true, editCondition = "bAllowValidationOnlyInGameFolder" ) )
TArray< FDirectoryPath > NonGameFoldersDirectoriesToProcess;
// Add folders located outside of /Game that you still want to process when bAllowValidationOnlyInGameFolder is checked, and which contain one of those tokens in their path
UPROPERTY( config, EditAnywhere, meta = ( LongPackageName, ConfigRestartRequired = true, editCondition = "bAllowValidationOnlyInGameFolder" ) )
TArray< FString > NonGameFoldersDirectoriesToProcessContainingToken;
UPROPERTY( config, EditAnywhere )
uint8 bDoesValidateOnSave : 1;
UPROPERTY( config, EditAnywhere, meta = ( ConfigRestartRequired = true ) )
TArray< FNamingConventionValidationClassDescription > ClassDescriptions;
UPROPERTY( config, EditAnywhere )
TArray< TSoftClassPtr< UObject > > ExcludedClassPaths;
UPROPERTY( transient )
TArray< UClass * > ExcludedClasses;
UPROPERTY( config, EditAnywhere )
FString BlueprintsPrefix;
void PostProcessSettings();
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
};

View File

@ -0,0 +1,12 @@
#pragma once
#include "NamingConventionValidationTypes.generated.h"
UENUM()
enum class ENamingConventionValidationResult : uint8
{
Invalid,
Valid,
Unknown,
Excluded
};

View File

@ -0,0 +1,5 @@
---
DisableFormat: 'true'
SortIncludes: 'false'
...