Skip to content

Commit a16f346

Browse files
authored
Improved PackageChecker validation and fixed a few ref assembly issues (#5713)
1 parent 34c8706 commit a16f346

File tree

13 files changed

+785
-103
lines changed

13 files changed

+785
-103
lines changed

Directory.Build.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
<Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
55

6+
<PropertyGroup>
7+
<!-- Workaround for broken dotnet sdk build publishing pipeline -->
8+
<BuildWithNetFrameworkHostedCompiler>false</BuildWithNetFrameworkHostedCompiler>
9+
</PropertyGroup>
610
<!-- Packaging props -->
711
<PropertyGroup>
812
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Reflection;
5+
6+
namespace PackageChecker
7+
{
8+
internal class ExportedEvent
9+
{
10+
public ExportedEvent(EventInfo e)
11+
{
12+
Signature = GetEventSignature(e);
13+
}
14+
15+
private string GetEventSignature(EventInfo e)
16+
{
17+
var eventType = GetTypeName(e.EventHandlerType);
18+
var eventName = e.Name;
19+
20+
var accessors = new[] { e.GetAddMethod(), e.GetRemoveMethod() }
21+
.Where(a => a.IsPublic || a.IsFamily) // Filter to include only public or protected
22+
.Select(a => a.Name.Contains("add_") ? "add" : "remove")
23+
.Distinct()
24+
.ToArray();
25+
26+
if (accessors.Length == 0)
27+
{
28+
// If there are no public or protected accessors, the event should not be included
29+
return string.Empty;
30+
}
31+
32+
var accessorsString = string.Join("/", accessors);
33+
return $"{eventType} {eventName} {{{accessorsString}}}";
34+
}
35+
36+
public string Signature { get; }
37+
38+
private static string GetTypeName(Type type)
39+
{
40+
if (type.IsGenericParameter)
41+
{
42+
return $"T{type.GenericParameterPosition}";
43+
}
44+
else if (type.IsGenericType)
45+
{
46+
var genericTypeDefinition = type.GetGenericTypeDefinition().Name;
47+
if (genericTypeDefinition.IndexOf('`') != -1) // Delegate definitions don't contain a backtick
48+
{
49+
genericTypeDefinition = genericTypeDefinition.Substring(0, genericTypeDefinition.IndexOf('`'));
50+
}
51+
var genericArguments = string.Join(", ", type.GetGenericArguments().Select(GetTypeName));
52+
return $"{genericTypeDefinition}<{genericArguments}>";
53+
}
54+
else
55+
{
56+
return type.Name;
57+
}
58+
}
59+
}
60+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Reflection;
5+
6+
namespace PackageChecker
7+
{
8+
internal class ExportedMethod
9+
{
10+
public ExportedMethod(MethodBase method)
11+
{
12+
Signature = GetMethodSignature(method);
13+
}
14+
15+
public string Signature { get; }
16+
17+
private static string GetMethodSignature(MethodBase method)
18+
{
19+
var parameterTypes = method.GetParameters()
20+
.Select(p => GetTypeName(p.ParameterType))
21+
.ToArray();
22+
23+
var genericArguments = method.IsGenericMethod ? $"<{string.Join(", ", method.GetGenericArguments().Select(arg => arg.Name))}>" : "";
24+
var returnType = GetTypeName(method is MethodInfo methodInfo ? methodInfo.ReturnType : method is ConstructorInfo ci ? ci.ReflectedType : null );
25+
var methodName = method.Name + genericArguments;
26+
27+
return $"{returnType} {methodName}({string.Join(", ", parameterTypes)})";
28+
}
29+
30+
private static string GetTypeName(Type type)
31+
{
32+
if (type is null)
33+
{
34+
return "__unknown__";
35+
}
36+
37+
if (type.IsGenericParameter)
38+
{
39+
return $"T{type.GenericParameterPosition}";
40+
}
41+
else if (type.IsGenericType)
42+
{
43+
var genericTypeDefinition = type.GetGenericTypeDefinition().Name;
44+
if (genericTypeDefinition.IndexOf('`') != -1) // Delegate definitions don't contain a backtick
45+
{
46+
genericTypeDefinition = genericTypeDefinition.Substring(0, genericTypeDefinition.IndexOf('`'));
47+
}
48+
var genericArguments = string.Join(", ", type.GetGenericArguments().Select(GetTypeName));
49+
return $"{genericTypeDefinition}<{genericArguments}>";
50+
}
51+
else
52+
{
53+
return type.Name;
54+
}
55+
}
56+
}
57+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Reflection;
5+
6+
namespace PackageChecker
7+
{
8+
internal class ExportedProperty
9+
{
10+
public ExportedProperty(PropertyInfo p)
11+
{
12+
Signature = GetPropertySignature(p);
13+
}
14+
15+
public string Signature { get; }
16+
17+
private static string GetPropertySignature(PropertyInfo property)
18+
{
19+
var propertyType = GetTypeName(property.PropertyType);
20+
var propertyName = property.Name;
21+
22+
var accessors = property.GetAccessors(true) // Include non-public to check visibility
23+
.Where(a => a.IsPublic || a.IsFamily) // Filter to include only public or protected
24+
.Select(a => a.Name.Contains("get_") ? "get" : "set")
25+
.Distinct()
26+
.ToArray();
27+
28+
if (accessors.Length == 0)
29+
{
30+
// If there are no public or protected accessors, the property should not be included
31+
return string.Empty;
32+
}
33+
34+
var accessorsString = string.Join("/", accessors);
35+
return $"{propertyType} {propertyName} {{{accessorsString}}}";
36+
}
37+
38+
private static string GetTypeName(Type type)
39+
{
40+
if (type.IsGenericParameter)
41+
{
42+
return $"T{type.GenericParameterPosition}";
43+
}
44+
else if (type.IsGenericType)
45+
{
46+
var genericTypeDefinition = type.GetGenericTypeDefinition().Name;
47+
if (genericTypeDefinition.IndexOf('`') != -1) // Delegate definitions don't contain a backtick
48+
{
49+
genericTypeDefinition = genericTypeDefinition.Substring(0, genericTypeDefinition.IndexOf('`'));
50+
}
51+
var genericArguments = string.Join(", ", type.GetGenericArguments().Select(GetTypeName));
52+
return $"{genericTypeDefinition}<{genericArguments}>";
53+
}
54+
else
55+
{
56+
return type.Name;
57+
}
58+
}
59+
}
60+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Reflection;
5+
using System.Text;
6+
7+
namespace PackageChecker
8+
{
9+
internal class ExportedType
10+
{
11+
public ExportedType(Type exportedType)
12+
{
13+
TypeName = exportedType.FullName;
14+
IsStatic = exportedType.IsAbstract && exportedType.IsSealed;
15+
IsInterface = exportedType.IsInterface;
16+
this.Assembly = exportedType.Assembly.GetName().Name;
17+
var constructors = exportedType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
18+
Constructors = new List<ExportedMethod>(constructors.Where(c => c.IsPublic | c.IsFamily).Select(c => new ExportedMethod(c)));
19+
var methods = exportedType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
20+
Methods = new List<ExportedMethod>(methods.Where(m => m.IsPublic | m.IsFamily).Select(m => new ExportedMethod(m)));
21+
var properties = exportedType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
22+
Properties = new List<ExportedProperty>(properties.Where(p => p.GetMethod.IsPublic | p.GetMethod.IsFamily).Select(p => new ExportedProperty(p)));
23+
var events = exportedType.GetEvents(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
24+
Events = new List<ExportedEvent>(events.Where(e => e.AddMethod.IsPublic | e.AddMethod.IsFamily).Select(e => new ExportedEvent(e)));
25+
ImplementedInterfaces = new List<string>(exportedType.GetInterfaces().Where(i => i.IsPublic).Select(i => i.FullName));
26+
}
27+
28+
public string TypeName { get; }
29+
public string Assembly { get; }
30+
public List<string> ImplementedInterfaces { get; }
31+
public bool IsStatic { get; }
32+
public bool IsInterface { get; }
33+
public List<ExportedMethod> Methods { get; }
34+
public List<ExportedProperty> Properties { get; }
35+
public List<ExportedEvent> Events { get; }
36+
public List<ExportedMethod> Constructors { get; }
37+
38+
public override string ToString()
39+
{
40+
var sb = new StringBuilder();
41+
sb.AppendLine($"TypeName: {TypeName}");
42+
sb.AppendLine($"Assembly: {Assembly}");
43+
sb.AppendLine($"IsStatic: {IsStatic}");
44+
sb.AppendLine($"IsInterface: {IsInterface}");
45+
sb.AppendLine("Constructors:");
46+
foreach (var constructor in Constructors)
47+
{
48+
sb.AppendLine($" {constructor.Signature}");
49+
}
50+
sb.AppendLine("Methods:");
51+
foreach (var method in Methods)
52+
{
53+
sb.AppendLine($" {method.Signature}");
54+
}
55+
sb.AppendLine("Properties:");
56+
foreach (var property in Properties)
57+
{
58+
sb.AppendLine($" {property.Signature}");
59+
}
60+
sb.AppendLine("Implemented Interfaces:");
61+
foreach (var implementedInterface in ImplementedInterfaces)
62+
{
63+
sb.AppendLine($" {implementedInterface}");
64+
}
65+
return sb.ToString();
66+
}
67+
68+
internal bool IsMatchingImplmentation(ExportedType candidateType)
69+
{
70+
bool matches = true;
71+
if (TypeName != candidateType.TypeName)
72+
return false;
73+
74+
foreach (var implementedInterface in ImplementedInterfaces)
75+
{
76+
if (!candidateType.ImplementedInterfaces.Any(i => i == implementedInterface))
77+
{
78+
Console.Error.WriteLine($"Type {TypeName} is missing implementing interface {implementedInterface} in the implementation.");
79+
matches = false;
80+
}
81+
}
82+
83+
foreach (var ev in Events)
84+
{
85+
if (!candidateType.Events.Any(e => e.Signature == ev.Signature))
86+
{
87+
Console.Error.WriteLine($"Type {TypeName} is missing event {ev.Signature} in the implementation.");
88+
matches = false;
89+
}
90+
}
91+
92+
foreach (var property in Properties)
93+
{
94+
if (!candidateType.Properties.Any(p => p.Signature == property.Signature))
95+
{
96+
Console.Error.WriteLine($"Type {TypeName} is missing property {property.Signature} in the implementation.");
97+
matches = false;
98+
}
99+
}
100+
101+
foreach (var method in Methods)
102+
{
103+
if (!candidateType.Methods.Any(m => m.Signature == method.Signature))
104+
{
105+
Console.Error.WriteLine($"Type {TypeName} is missing method {method.Signature} in the implementation.");
106+
matches = false;
107+
}
108+
}
109+
110+
foreach (var constructor in Constructors)
111+
{
112+
if (!candidateType.Constructors.Any(c => c.Signature == constructor.Signature))
113+
{
114+
Console.Error.WriteLine($"Type {TypeName} is missing constructor {constructor.Signature} in the implementation.");
115+
matches = false;
116+
}
117+
}
118+
119+
return matches;
120+
}
121+
}
122+
}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
55
<TargetFramework>net8.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>disable</Nullable>
8+
<IsShippingAssembly>false</IsShippingAssembly>
89
</PropertyGroup>
910

1011
<ItemGroup>
1112
<PackageReference Include="System.CommandLine.DragonFruit" Version="0.4.0-alpha.22272.1" />
1213
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.0.0" />
14+
<PackageReference Include="NuGet.PackageManagement" Version="6.12.1" />
15+
<PackageReference Include="NuGet.Protocol" Version="6.12.1" />
16+
<PackageReference Include="Nuget.Packaging" Version="6.12.1" />
17+
<PackageReference Include="NuGet.Frameworks" Version="6.12.1" />
1318
</ItemGroup>
1419

1520
</Project>

0 commit comments

Comments
 (0)