Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,57 +74,6 @@ public static void WriteAttribute(ICodeWriter sourceCodeWriter, Compilation comp
}
}

public static void WriteAttributeMetadata(ICodeWriter sourceCodeWriter, Compilation compilation,
AttributeData attributeData, string targetElement, string? targetMemberName, string? targetTypeName, bool includeClassMetadata = false)
{
sourceCodeWriter.Append("new global::TUnit.Core.AttributeMetadata");
sourceCodeWriter.Append(" { ");
sourceCodeWriter.Append($"Instance = {GetAttributeObjectInitializer(compilation, attributeData)}, ");
sourceCodeWriter.Append($"TargetElement = global::TUnit.Core.TestAttributeTarget.{targetElement}, ");

if (targetMemberName != null)
{
sourceCodeWriter.Append($"TargetMemberName = \"{targetMemberName}\", ");
}

if (targetTypeName != null)
{
sourceCodeWriter.Append($"TargetType = typeof({targetTypeName}), ");
}

// Add ClassMetadata if requested and not a system attribute
if (includeClassMetadata && attributeData.AttributeClass?.ContainingNamespace?.ToDisplayString()?.StartsWith("System") != true)
{
sourceCodeWriter.Append("ClassMetadata = ");
SourceInformationWriter.GenerateClassInformation(sourceCodeWriter, compilation, attributeData.AttributeClass!);
sourceCodeWriter.Append(", ");
}

if (attributeData.ConstructorArguments.Length > 0)
{
sourceCodeWriter.Append("ConstructorArguments = new object?[] { ");

foreach (var typedConstant in attributeData.ConstructorArguments)
{
sourceCodeWriter.Append($"{TypedConstantParser.GetRawTypedConstantValue(typedConstant)}, ");
}

sourceCodeWriter.Append("}, ");
}

if (attributeData.NamedArguments.Length > 0)
{
sourceCodeWriter.Append("NamedArguments = new global::System.Collections.Generic.Dictionary<string, object?>() { ");
foreach (var namedArg in attributeData.NamedArguments)
{
sourceCodeWriter.Append($"[\"{namedArg.Key}\"] = {TypedConstantParser.GetRawTypedConstantValue(namedArg.Value)}, ");
}
sourceCodeWriter.Append("}, ");
}

sourceCodeWriter.Append("}");
}

public static string GetAttributeObjectInitializer(Compilation compilation,
AttributeData attributeData)
{
Expand Down Expand Up @@ -195,8 +144,8 @@ public static void WriteAttributeWithoutSyntax(ICodeWriter sourceCodeWriter, Att
var attributeName = attributeData.AttributeClass!.GloballyQualified();

// Skip if any constructor arguments contain compiler-generated types
if (attributeData.ConstructorArguments.Any(arg =>
arg is { Kind: TypedConstantKind.Type, Value: ITypeSymbol typeSymbol } &&
if (attributeData.ConstructorArguments.Any(arg =>
arg is { Kind: TypedConstantKind.Type, Value: ITypeSymbol typeSymbol } &&
typeSymbol.IsCompilerGeneratedType()))
{
return;
Expand Down
83 changes: 17 additions & 66 deletions TUnit.Core.SourceGenerator/Helpers/FileNameHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,57 +8,6 @@ namespace TUnit.Core.SourceGenerator.Helpers;
/// </summary>
internal static class FileNameHelper
{
/// <summary>
/// Generates a deterministic filename for a test class.
/// Format: {Namespace}_{ClassName}_{GenericArgs}.g.cs
/// </summary>
/// <param name="typeSymbol">The type symbol for the test class</param>
/// <returns>A deterministic filename like "MyNamespace_MyClass_T.g.cs"</returns>
public static string GetDeterministicFileName(INamedTypeSymbol typeSymbol)
{
var sb = new StringBuilder();

// Add namespace
if (!typeSymbol.ContainingNamespace.IsGlobalNamespace)
{
sb.Append(SanitizeForFileName(typeSymbol.ContainingNamespace.ToDisplayString()));
sb.Append('_');
}

// Add all containing types (outer classes first, then inner classes)
var containingTypes = new List<string>();
var currentType = typeSymbol;
while (currentType != null)
{
containingTypes.Add(SanitizeForFileName(currentType.Name));
currentType = currentType.ContainingType;
}

// Reverse to get outer-to-inner order
containingTypes.Reverse();

// Append containing types from outer to inner
for (int i = 0; i < containingTypes.Count; i++)
{
if (i > 0) sb.Append('_');
sb.Append(containingTypes[i]);
}

// Add generic type arguments if any (for the innermost type)
if (typeSymbol.TypeArguments.Length > 0)
{
sb.Append('_');
for (int i = 0; i < typeSymbol.TypeArguments.Length; i++)
{
if (i > 0) sb.Append('_');
sb.Append(SanitizeForFileName(typeSymbol.TypeArguments[i].Name));
}
}

sb.Append(".g.cs");
return sb.ToString();
}

/// <summary>
/// Generates a deterministic filename for a test method.
/// Format: {Namespace}_{ClassName}_{MethodName}__{ParameterTypes}.g.cs
Expand Down Expand Up @@ -132,24 +81,26 @@ public static string GetDeterministicFileNameForMethod(INamedTypeSymbol typeSymb
/// </summary>
private static string SanitizeForFileName(string input)
{
var sb = new StringBuilder(input.Length);

foreach (var c in input)
return string.Create(input.Length, input, (span, input) =>
{
// Replace invalid filename characters and special type characters with underscore
if (c == '<' || c == '>' || c == ':' || c == '"' || c == '/' || c == '\\' ||
c == '|' || c == '?' || c == '*' || c == '.' || c == ',' || c == ' ' ||
c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}')
{
sb.Append('_');
}
else
var index = 0;
foreach (var c in input)
{
sb.Append(c);
// Replace invalid filename characters and special type characters with underscore
if (c == '<' || c == '>' || c == ':' || c == '"' || c == '/' || c == '\\' ||
c == '|' || c == '?' || c == '*' || c == '.' || c == ',' || c == ' ' ||
c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}')
{
span[index] = '_';
}
else
{
span[index] = c;
}

index++;
}
}

return sb.ToString();
});
}

}
Loading