Add Naming Convention Plugin
This commit is contained in:
parent
2543e7953f
commit
edb2daa78b
@ -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_
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
77
Pawn_Unreal/Plugins/NamingConventionValidation/.gitignore
vendored
Normal file
77
Pawn_Unreal/Plugins/NamingConventionValidation/.gitignore
vendored
Normal 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/*
|
||||
21
Pawn_Unreal/Plugins/NamingConventionValidation/LICENSE
Normal file
21
Pawn_Unreal/Plugins/NamingConventionValidation/LICENSE
Normal 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.
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
9
Pawn_Unreal/Plugins/NamingConventionValidation/README.md
Normal file
9
Pawn_Unreal/Plugins/NamingConventionValidation/README.md
Normal 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.
|
||||
@ -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"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
#include "NamingConventionValidationLog.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY( LogNamingConventionValidation );
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -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();
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN( LogNamingConventionValidation, Verbose, All )
|
||||
@ -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" );
|
||||
}
|
||||
};
|
||||
@ -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
|
||||
};
|
||||
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "NamingConventionValidationTypes.generated.h"
|
||||
|
||||
UENUM()
|
||||
enum class ENamingConventionValidationResult : uint8
|
||||
{
|
||||
Invalid,
|
||||
Valid,
|
||||
Unknown,
|
||||
Excluded
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
DisableFormat: 'true'
|
||||
SortIncludes: 'false'
|
||||
|
||||
...
|
||||
Loading…
x
Reference in New Issue
Block a user