Metadata Usage Finder
Metadata Usage Finder
org
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using EpicGames.UHT.Types;
using static Citrus.Plugins.SpecifierReferenceViewer.ReferenceGenerator.Tag;
namespace Citrus.Plugins.SpecifierReferenceViewer.ReferenceGenerator;
/// <summary>
/// Provides information about a specific use of a metadata specifier.
/// </summary>
internal sealed record MetadataUsageInfo(string Key, string Value, Tag Tag, string
FilePath);
/// <summary>
/// Provides a method for finding all metadata used in the source code.
/// </summary>
internal static class MetadataUsageFinder
{
/// <summary>
/// If a metadata key starts with any value in this list, it will be excluded
from the result. Technically, these
/// can be used, but all are UHT-generated intermediate metadata that should
not be directly used in code.
/// </summary>
private static readonly IReadOnlyList<string> FilterPrefixes
= new[]
{
// Intermediate metadata generated by UHT.
"CPP_"
};
/// <summary>
/// Searches for all metadata usages in a set of packages.
/// </summary>
public static IEnumerable<MetadataUsageInfo>
FindAllMetadataUsages(IEnumerable<UhtPackage> packages)
{
foreach (UhtPackage package in packages)
{
foreach (MetadataUsageInfo metadata in FindAllMetadataUsages(package))
{
yield return metadata;
}
}
}
/// <summary>
/// Searches for all metadata usages in a UHT type.
/// </summary>
private static IEnumerable<MetadataUsageInfo> FindAllMetadataUsages(UhtType
type)
{
if (DetermineTagFromType(type) is Tag tag and not default(Tag))
{
if (type.MetaData is not null && !type.MetaData.IsEmpty())
{
foreach (var (key, value) in type.MetaData.Dictionary!)
{
bool isFiltered =
FilterPrefixes.Any(prefix => key.Name.StartsWith(prefix,
StringComparison.OrdinalIgnoreCase)) ||
Filter.Contains(key.Name);
if (!isFiltered)
{
// Special handling is required to differentiate between
UENUM and UMETA.
if (tag is UENUM && key.Index >= 0)
{
yield return new MetadataUsageInfo(key.Name, value,
UMETA, type.HeaderFile.FilePath);
}
else
{
yield return new MetadataUsageInfo(key.Name, value,
tag, type.HeaderFile.FilePath);
}
}
}
}
}
/// <summary>
/// Determines the appropriate tag to use for a UHT type.
/// </summary>
private static Tag DetermineTagFromType(UhtType type)
{
return type switch
{
UhtEnum => UENUM,
UhtScriptStruct => USTRUCT,
UhtClass @class => IsInterface(@class) ? UINTERFACE : UCLASS,
UhtFunction function => IsDelegate(function) ? UDELEGATE : UFUNCTION,
UhtProperty property => IsParameter(property) ? UPARAM : UPROPERTY,
_ => default
};
}
/// <summary>
/// Determines whether a UPROPERTY is actually a UPARAM.
/// </summary>
private static bool IsParameter(UhtProperty property)
=> property.PropertyCategory == UhtPropertyCategory.Return
|| property.PropertyCategory == UhtPropertyCategory.RegularParameter
|| property.PropertyCategory == UhtPropertyCategory.ReplicatedParameter;
/// <summary>
/// Determines whether a UCLASS is actually an UINTERFACE.
/// </summary>
private static bool IsInterface(UhtClass @class) =>
@class.ClassFlags.HasFlag(EpicGames.Core.EClassFlags.Interface);
/// <summary>
/// Determines whether a UFUNCTION is actually a UDELEGATE.
/// </summary>
private static bool IsDelegate(UhtFunction function)
=> function.FunctionType == UhtFunctionType.Delegate
|| function.FunctionType == UhtFunctionType.SparseDelegate;
}