diff --git a/Pawn_Unreal/Config/DefaultEditor.ini b/Pawn_Unreal/Config/DefaultEditor.ini index 42106a6..ea68b7f 100644 --- a/Pawn_Unreal/Config/DefaultEditor.ini +++ b/Pawn_Unreal/Config/DefaultEditor.ini @@ -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_ + diff --git a/Pawn_Unreal/Pawn.uproject b/Pawn_Unreal/Pawn.uproject index 830cd9a..818a16a 100644 --- a/Pawn_Unreal/Pawn.uproject +++ b/Pawn_Unreal/Pawn.uproject @@ -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 + } + ] } \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/.gitignore b/Pawn_Unreal/Plugins/NamingConventionValidation/.gitignore new file mode 100644 index 0000000..1daca8b --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/.gitignore @@ -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-.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/* diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/LICENSE b/Pawn_Unreal/Plugins/NamingConventionValidation/LICENSE new file mode 100644 index 0000000..8b39944 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/LICENSE @@ -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. diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/NamingConventionValidation.uplugin b/Pawn_Unreal/Plugins/NamingConventionValidation/NamingConventionValidation.uplugin new file mode 100644 index 0000000..72501fc --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/NamingConventionValidation.uplugin @@ -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" + } + ] +} diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/README.md b/Pawn_Unreal/Plugins/NamingConventionValidation/README.md new file mode 100644 index 0000000..87087ac --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/README.md @@ -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. diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/NamingConventionValidation.Build.cs b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/NamingConventionValidation.Build.cs new file mode 100644 index 0000000..bcc3975 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/NamingConventionValidation.Build.cs @@ -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" + } + ); + } + } +} diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/EditorNamingValidatorBase.cpp b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/EditorNamingValidatorBase.cpp new file mode 100644 index 0000000..b7b3aa5 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/EditorNamingValidatorBase.cpp @@ -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; +} diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/EditorNamingValidatorSubsystem.cpp b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/EditorNamingValidatorSubsystem.cpp new file mode 100644 index 0000000..eede138 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/EditorNamingValidatorSubsystem.cpp @@ -0,0 +1,472 @@ +#include "EditorNamingValidatorSubsystem.h" + +#include "NamingConventionValidationSettings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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 diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationCommandlet.cpp b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationCommandlet.cpp new file mode 100644 index 0000000..a7e9bf4 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationCommandlet.cpp @@ -0,0 +1,56 @@ +#include "NamingConventionValidationCommandlet.h" + +#include "NamingConventionValidationLog.h" +#include "EditorNamingValidatorSubsystem.h" + +#include +#include +#include +#include + +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; +} diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationLog.cpp b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationLog.cpp new file mode 100644 index 0000000..dba9a3c --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationLog.cpp @@ -0,0 +1,3 @@ +#include "NamingConventionValidationLog.h" + +DEFINE_LOG_CATEGORY( LogNamingConventionValidation ); \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationModule.cpp b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationModule.cpp new file mode 100644 index 0000000..bc2b359 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationModule.cpp @@ -0,0 +1,237 @@ +#include "NamingConventionValidationModule.h" + +#include "EditorNamingValidatorSubsystem.h" +#include "NamingConventionValidationCommandlet.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationSettings.cpp b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationSettings.cpp new file mode 100644 index 0000000..95d7486 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Private/NamingConventionValidationSettings.cpp @@ -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 diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/EditorNamingValidatorBase.h b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/EditorNamingValidatorBase.h new file mode 100644 index 0000000..bdd166b --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/EditorNamingValidatorBase.h @@ -0,0 +1,29 @@ +#pragma once + +#include "NamingConventionValidationTypes.h" + +#include +#include + +#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; +}; diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/EditorNamingValidatorSubsystem.h b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/EditorNamingValidatorSubsystem.h new file mode 100644 index 0000000..0fa6152 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/EditorNamingValidatorSubsystem.h @@ -0,0 +1,46 @@ +#pragma once + +#include "NamingConventionValidationTypes.h" + +#include +#include + +#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; +}; diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationCommandlet.h b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationCommandlet.h new file mode 100644 index 0000000..12f55f5 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationCommandlet.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#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(); +}; diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationLog.h b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationLog.h new file mode 100644 index 0000000..b98420e --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationLog.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +DECLARE_LOG_CATEGORY_EXTERN( LogNamingConventionValidation, Verbose, All ) \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationModule.h b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationModule.h new file mode 100644 index 0000000..3065cfd --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationModule.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include + +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" ); + } +}; diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationSettings.h b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationSettings.h new file mode 100644 index 0000000..1a77710 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationSettings.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include + +#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 +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationTypes.h b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationTypes.h new file mode 100644 index 0000000..a349b36 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/Source/NamingConventionValidation/Public/NamingConventionValidationTypes.h @@ -0,0 +1,12 @@ +#pragma once + +#include "NamingConventionValidationTypes.generated.h" + +UENUM() +enum class ENamingConventionValidationResult : uint8 +{ + Invalid, + Valid, + Unknown, + Excluded +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/NamingConventionValidation/_clang-format b/Pawn_Unreal/Plugins/NamingConventionValidation/_clang-format new file mode 100644 index 0000000..2196074 --- /dev/null +++ b/Pawn_Unreal/Plugins/NamingConventionValidation/_clang-format @@ -0,0 +1,5 @@ +--- +DisableFormat: 'true' +SortIncludes: 'false' + +...