// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using EpicGames.Core; using EpicGames.UHT.Tables; using EpicGames.UHT.Types; using EpicGames.UHT.Utils; namespace EpicGames.UHT.Parsers { /// /// Collection of default specifiers that apply to everything /// [UnrealHeaderTool] [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Attribute accessed method")] [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Attribute accessed method")] public static class UhtDefaultSpecifiers { private static void SetMetaData(UhtSpecifierContext specifierContext, StringView key, StringView value) { if (key.Length == 0) { specifierContext.MessageSite.LogError("Invalid metadata, name can not be blank"); return; } // Trim the leading and ending whitespace ReadOnlySpan span = value.Span; int start = 0; int end = value.Length; for (; start < end; ++start) { if (!UhtFCString.IsWhitespace(span[start])) { break; } } for (; start < end; --end) { if (!UhtFCString.IsWhitespace(span[end - 1])) { break; } } // Trim any quotes //COMPATIBILITY-TODO - This doesn't handle strings that end in an escaped ". This is a bug in old UHT. // Only remove quotes if we have quotes at both ends and the last isn't escaped. if (start < end && span[start] == '"') { ++start; } if (start < end && span[end - 1] == '"') { --end; } // Get the trimmed string value = new StringView(value, start, end - start); specifierContext.MetaData.CheckedAdd(key.ToString(), specifierContext.MetaNameIndex, value.ToString()); } private static void SetMetaData(UhtSpecifierContext specifierContext, StringView key, bool value) { SetMetaData(specifierContext, key, value ? "true" : "false"); } #region Specifiers [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.KeyValuePairList, When = UhtSpecifierWhen.Immediate)] private static void MetaSpecifier(UhtSpecifierContext specifierContext, List> value) { foreach (KeyValuePair kvp in (List>)value) { SetMetaData(specifierContext, kvp.Key, kvp.Value); } } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.String, When = UhtSpecifierWhen.Immediate)] private static void DisplayNameSpecifier(UhtSpecifierContext specifierContext, StringView value) { SetMetaData(specifierContext, "DisplayName", value); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.String, When = UhtSpecifierWhen.Immediate)] private static void FriendlyNameSpecifier(UhtSpecifierContext specifierContext, StringView value) { SetMetaData(specifierContext, "FriendlyName", value); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void BlueprintInternalUseOnlySpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, "BlueprintInternalUseOnly", true); SetMetaData(specifierContext, "BlueprintType", true); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void BlueprintInternalUseOnlyHierarchicalSpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, "BlueprintInternalUseOnlyHierarchical", true); SetMetaData(specifierContext, "BlueprintInternalUseOnly", true); SetMetaData(specifierContext, "BlueprintType", true); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void BlueprintTypeSpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, "BlueprintType", true); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void NotBlueprintTypeSpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, "NotBlueprintType", true); specifierContext.MetaData.Remove("BlueprintType", specifierContext.MetaNameIndex); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void BlueprintableSpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, UhtNames.IsBlueprintBase, true); SetMetaData(specifierContext, "BlueprintType", true); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void CallInEditorSpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, "CallInEditor", true); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void NotBlueprintableSpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, UhtNames.IsBlueprintBase, false); specifierContext.MetaData.Remove("BlueprintType", specifierContext.MetaNameIndex); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.String, When = UhtSpecifierWhen.Immediate)] private static void CategorySpecifier(UhtSpecifierContext specifierContext, StringView value) { SetMetaData(specifierContext, "Category", value); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void ExperimentalSpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, "DevelopmentStatus", "Experimental"); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void EarlyAccessPreviewSpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, "DevelopmentStatus", "EarlyAccess"); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void DocumentationPolicySpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, UhtNames.DocumentationPolicy, "Strict"); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.String, When = UhtSpecifierWhen.Immediate)] private static void SparseClassDataTypeSpecifier(UhtSpecifierContext specifierContext, StringView value) { SetMetaData(specifierContext, "SparseClassDataType", value); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void StateStreamStaticStateSpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, "StateStreamStaticState", true); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void StateStreamDynamicStateSpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, "StateStreamDynamicState", true); } [UhtSpecifier(Extends = UhtTableNames.Default, ValueType = UhtSpecifierValueType.None, When = UhtSpecifierWhen.Immediate)] private static void StateStreamHandleSpecifier(UhtSpecifierContext specifierContext) { SetMetaData(specifierContext, "StateStreamHandle", true); } #endregion #region Validators [UhtSpecifierValidator(Name = "UIMin", Extends = UhtTableNames.Default)] [UhtSpecifierValidator(Name = "UIMax", Extends = UhtTableNames.Default)] [UhtSpecifierValidator(Name = "ClampMin", Extends = UhtTableNames.Default)] [UhtSpecifierValidator(Name = "ClampMax", Extends = UhtTableNames.Default)] private static void ValidateNumeric(UhtType type, UhtMetaData metaData, UhtMetaDataKey key, StringView value) { if (!UhtFCString.IsNumeric(value.Span)) { type.LogError($"Metadata value for '{key}' is non-numeric : '{value}'"); } } [UhtSpecifierValidator(Extends = UhtTableNames.Default)] private static void DevelopmentStatusSpecifierValidator(UhtType type, UhtMetaData metaData, UhtMetaDataKey key, StringView value) { string[] allowedValues = { "EarlyAccess", "Experimental" }; foreach (string allowedValue in allowedValues) { if (value.Equals(allowedValue, StringComparison.OrdinalIgnoreCase)) { return; } } type.LogError($"'{key.Name}' metadata was '{value}' but it must be {UhtUtilities.MergeTypeNames(allowedValues, "or", false)}"); } [UhtSpecifierValidator(Extends = UhtTableNames.Default)] private static void DocumentationPolicySpecifierValidator(UhtType type, UhtMetaData metaData, UhtMetaDataKey key, StringView value) { const string StrictValue = "Strict"; if (!value.Span.Equals(StrictValue, StringComparison.OrdinalIgnoreCase)) { type.LogError(metaData.LineNumber, $"'{key}' metadata was '{value}' but it must be '{StrictValue}'"); } } [UhtSpecifierValidator(Extends = UhtTableNames.Default)] private static void UnitsSpecifierValidator(UhtType type, UhtMetaData metaData, UhtMetaDataKey key, StringView value) { // Check for numeric property if (type is UhtProperty) { if (type is not UhtNumericProperty && type is not UhtStructProperty) { type.LogError("'Units' meta data can only be applied to numeric and struct properties"); } } if (!type.Session.Config!.IsValidUnits(value)) { type.LogError($"Unrecognized units '{value}' specified for '{type.FullName}'"); } } /// /// Validator for metadata on various types which must be a long path name to another type/function/etc. /// [UhtSpecifierValidator(Name = "MustImplement", Extends = UhtTableNames.PropertyMember)] [UhtSpecifierValidator(Name = "ObjectMustImplement", Extends = UhtTableNames.PropertyMember)] [UhtSpecifierValidator(Name = "AllowedClasses", Extends = UhtTableNames.PropertyMember)] [UhtSpecifierValidator(Name = "DisallowedClasses", Extends = UhtTableNames.PropertyMember)] [UhtSpecifierValidator(Name = "MetaClass", Extends = UhtTableNames.PropertyMember)] [UhtSpecifierValidator(Name = "MetaStruct", Extends = UhtTableNames.PropertyMember)] [UhtSpecifierValidator(Name = "BaseClass", Extends = UhtTableNames.PropertyMember)] [UhtSpecifierValidator(Name = "BaseStruct", Extends = UhtTableNames.PropertyMember)] [UhtSpecifierValidator(Name = "BitmaskEnum", Extends = UhtTableNames.PropertyMember)] [UhtSpecifierValidator(Name = "HasNativeBreak", Extends = UhtTableNames.ScriptStruct)] [UhtSpecifierValidator(Name = "HasNativeMake", Extends = UhtTableNames.ScriptStruct)] [UhtSpecifierValidator(Name = "ActorClassToPoll", Extends = UhtTableNames.Function)] [UhtSpecifierValidator(Name = "ComponentClass", Extends = UhtTableNames.Function)] private static void LongPathNameMetaDataSpecifierValidator(UhtType type, UhtMetaData metaData, UhtMetaDataKey key, StringView value) { foreach (string element in value.ToString().Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { if (!UhtUtilities.IsValidLongPathName(element)) { UhtType? found = type.FindType(UhtFindOptions.TypesMask | UhtFindOptions.EngineName | UhtFindOptions.NoSelf, element); string message = found is not null ? $"MetaData {key} on {type.EngineName} has value '{element}' which is not a valid long path name. Did you mean '{found.PathName}' ?" : $"MetaData {key} on {type.EngineName} has value '{element}' which is not a valid long path name. Replace it with a full path name specifying the package containing the type."; type.LogError(message); } } } #endregion } }