Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial version of ApiCompatibility #16817

Merged
merged 7 commits into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ obj/

*.binlog

sdk.sln.DotSettings.user
# User specific files
*.user

# Debian and python stuff
*.dsc
Expand Down
4 changes: 4 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@
/src/Tests/dotnet-watch.Tests/ @captainsafia, @pranavkm, @mkArtakMSFT
/src/Tests/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests/ @captainsafia, @pranavkm, @mkArtakMSFT
/src/BuiltInTools/ @captainsafia, @pranavkm, @mkArtakMSFT

# Compatibility tools owned by runtime team
/src/Compatibility/ @Anipik, @safern, @ericstj
/src/Tests/Microsoft.DotNet.ApiCompatibility/ @Anipik, @safern, @ericstj
17 changes: 17 additions & 0 deletions sdk.sln
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.NativeWrap
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.Razor.SourceGenerators.Tests", "src\Tests\Microsoft.NET.Sdk.Razor.SourceGenerators.Tests\Microsoft.NET.Sdk.Razor.SourceGenerators.Tests.csproj", "{A71FC21D-D90A-49B5-9B5A-AD4776287B55}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Compatibility", "Compatibility", "{AF683E5C-421E-4DE0-ADD7-9841E5D12BFA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ApiCompatibility", "src\Compatibility\Microsoft.DotNet.ApiCompatibility\Microsoft.DotNet.ApiCompatibility.csproj", "{3F5A028C-C51B-434A-8C10-37680CD2635C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.ApiCompatibility.Tests", "src\Tests\Microsoft.DotNet.ApiCompatibility.Tests\Microsoft.DotNet.ApiCompatibility.Tests.csproj", "{24F084ED-35BB-401E-89F5-63E5E22C3B3B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -615,6 +621,14 @@ Global
{A71FC21D-D90A-49B5-9B5A-AD4776287B55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A71FC21D-D90A-49B5-9B5A-AD4776287B55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A71FC21D-D90A-49B5-9B5A-AD4776287B55}.Release|Any CPU.Build.0 = Release|Any CPU
{3F5A028C-C51B-434A-8C10-37680CD2635C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F5A028C-C51B-434A-8C10-37680CD2635C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F5A028C-C51B-434A-8C10-37680CD2635C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F5A028C-C51B-434A-8C10-37680CD2635C}.Release|Any CPU.Build.0 = Release|Any CPU
{24F084ED-35BB-401E-89F5-63E5E22C3B3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{24F084ED-35BB-401E-89F5-63E5E22C3B3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24F084ED-35BB-401E-89F5-63E5E22C3B3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24F084ED-35BB-401E-89F5-63E5E22C3B3B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -728,6 +742,9 @@ Global
{1BBFA19C-03F0-4D27-9D0D-0F8172642107} = {71A9F549-0EB6-41F9-BC16-4A6C5007FC91}
{E97E9E7F-11B4-42F7-8B55-D0451F5E82A0} = {8F22FBD6-BDC8-431E-8402-B7460D3A9724}
{A71FC21D-D90A-49B5-9B5A-AD4776287B55} = {580D1AE7-AA8F-4912-8B76-105594E00B3B}
{AF683E5C-421E-4DE0-ADD7-9841E5D12BFA} = {22AB674F-ED91-4FBC-BFEE-8A1E82F9F05E}
{3F5A028C-C51B-434A-8C10-37680CD2635C} = {AF683E5C-421E-4DE0-ADD7-9841E5D12BFA}
{24F084ED-35BB-401E-89F5-63E5E22C3B3B} = {580D1AE7-AA8F-4912-8B76-105594E00B3B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FB8F26CE-4DE6-433F-B32A-79183020BBD6}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.CodeAnalysis;
using System;

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
public class CompatDifference : IDiagnostic, IEquatable<CompatDifference>
safern marked this conversation as resolved.
Show resolved Hide resolved
{
public string DiagnosticId { get; }
public DifferenceType Type { get; }
public virtual string Message { get; }
public string ReferenceId { get; }

private CompatDifference() { }

public CompatDifference(string id, string message, DifferenceType type, ISymbol member)
safern marked this conversation as resolved.
Show resolved Hide resolved
: this(id, message, type, member.GetDocumentationCommentId())
safern marked this conversation as resolved.
Show resolved Hide resolved
{
}

public CompatDifference(string id, string message, DifferenceType type, string memberId)
{
DiagnosticId = id;
Message = message;
Type = type;
ReferenceId = memberId;
}

public bool Equals(CompatDifference other) =>
Type == other.Type &&
DiagnosticId.Equals(other.DiagnosticId, StringComparison.OrdinalIgnoreCase) &&
ReferenceId.Equals(other.ReferenceId, StringComparison.OrdinalIgnoreCase) &&
Message.Equals(other.Message, StringComparison.OrdinalIgnoreCase);

public override int GetHashCode() =>
HashCode.Combine(ReferenceId, DiagnosticId, Message, Type);

public override string ToString() => $"{DiagnosticId} : {Message}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
public enum DifferenceType
{
Changed,
Added,
Removed
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.CodeAnalysis;

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
internal class AccessibilityFilter : IDiffingFilter
safern marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly bool _includeInternalSymbols;

internal AccessibilityFilter(bool includeInternalSymbols)
{
_includeInternalSymbols = includeInternalSymbols;
}

public bool Include(ISymbol symbol) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ericstj. Will open an issue for this one as well. Filtering needs to be completed as I'm also missing filtering out attributes based on settings (I guess that will be another filter) but I will take note of these scenarios!

symbol.DeclaredAccessibility == Accessibility.Public ||
symbol.DeclaredAccessibility == Accessibility.Protected ||
symbol.DeclaredAccessibility == Accessibility.ProtectedOrInternal ||
(_includeInternalSymbols && symbol.DeclaredAccessibility != Accessibility.Private);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.CodeAnalysis;

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
public interface IDiffingFilter
safern marked this conversation as resolved.
Show resolved Hide resolved
{
bool Include(ISymbol symbol);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
public interface IDiagnostic
{
string DiagnosticId { get; }
string ReferenceId { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
public class MapperVisitor
{
public void Visit<T>(ElementMapper<T> mapper)
{
if (mapper is AssemblySetMapper assemblySetMapper)
{
Visit(assemblySetMapper);
}
else if (mapper is AssemblyMapper assemblyMapper)
{
Visit(assemblyMapper);
}
else if (mapper is NamespaceMapper nsMapper)
{
Visit(nsMapper);
}
else if (mapper is TypeMapper typeMapper)
{
Visit(typeMapper);
}
else if (mapper is MemberMapper memberMapper)
{
Visit(memberMapper);
}
}

public virtual void Visit(AssemblySetMapper mapper)
{
foreach (AssemblyMapper assembly in mapper.GetAssemblies())
safern marked this conversation as resolved.
Show resolved Hide resolved
{
Visit(assembly);
}
}

public virtual void Visit(AssemblyMapper mapper)
{
foreach (NamespaceMapper nsMapper in mapper.GetNamespaces())
{
Visit(nsMapper);
}
}

public virtual void Visit(NamespaceMapper mapper)
{
foreach (TypeMapper type in mapper.GetTypes())
{
Visit(type);
}
}

public virtual void Visit(TypeMapper mapper)
{
foreach (TypeMapper type in mapper.GetNestedTypes())
{
Visit(type);
}

foreach (MemberMapper member in mapper.GetMembers())
{
Visit(member);
}
}

public virtual void Visit(MemberMapper mapper) { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.CodeAnalysis;
using System.Collections.Generic;

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
public class AssemblyMapper : ElementMapper<IAssemblySymbol>
{
private Dictionary<INamespaceSymbol, NamespaceMapper> _namespaces;

public AssemblyMapper(DiffingSettings settings) : base(settings) { }

public IEnumerable<NamespaceMapper> GetNamespaces()
{
if (_namespaces == null)
{
_namespaces = new Dictionary<INamespaceSymbol, NamespaceMapper>(Settings.EqualityComparer);
Dictionary<INamespaceSymbol, List<INamedTypeSymbol>> typeForwards;
if (Left != null)
{
typeForwards = ResolveTypeForwards(Left, Settings.EqualityComparer);
AddOrCreateMappers(Left.GlobalNamespace, 0);
}

if (Right != null)
{
typeForwards = ResolveTypeForwards(Right, Settings.EqualityComparer);
AddOrCreateMappers(Right.GlobalNamespace, 1);
}

void AddOrCreateMappers(INamespaceSymbol ns, int index)
{
Stack<INamespaceSymbol> stack = new();
stack.Push(ns);
while (stack.Count > 0)
{
INamespaceSymbol symbol = stack.Pop();
if (typeForwards.TryGetValue(symbol, out List<INamedTypeSymbol> forwardedTypes) || symbol.GetTypeMembers().Length > 0)
{
if (!_namespaces.TryGetValue(symbol, out NamespaceMapper mapper))
{
mapper = new NamespaceMapper(Settings);
_namespaces.Add(symbol, mapper);
}

mapper.AddElement(symbol, index);
mapper.AddForwardedTypes(forwardedTypes ?? new List<INamedTypeSymbol>(), index);
}

foreach (INamespaceSymbol child in symbol.GetNamespaceMembers())
stack.Push(child);
}
}

static Dictionary<INamespaceSymbol, List<INamedTypeSymbol>> ResolveTypeForwards(IAssemblySymbol assembly, IEqualityComparer<ISymbol> comparer)
{
Dictionary<INamespaceSymbol, List<INamedTypeSymbol>> typeForwards = new(comparer);
foreach (INamedTypeSymbol symbol in assembly.GetForwardedTypes())
{
if (symbol.TypeKind != TypeKind.Error)
{
if (!typeForwards.TryGetValue(symbol.ContainingNamespace, out List<INamedTypeSymbol> types))
{
types = new List<INamedTypeSymbol>();
typeForwards.Add(symbol.ContainingNamespace, types);
}

types.Add(symbol);
}
else
{
// TODO: Log Warning;
}
}

return typeForwards;
}
}

return _namespaces.Values;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
safern marked this conversation as resolved.
Show resolved Hide resolved

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
public class AssemblySetMapper : ElementMapper<IEnumerable<IAssemblySymbol>>
{
private Dictionary<IAssemblySymbol, AssemblyMapper> _assemblies;

public AssemblySetMapper(DiffingSettings settings) : base(settings) { }

public IEnumerable<AssemblyMapper> GetAssemblies()
{
if (_assemblies == null)
{
_assemblies = new Dictionary<IAssemblySymbol, AssemblyMapper>(Settings.EqualityComparer);

if (Left != null)
safern marked this conversation as resolved.
Show resolved Hide resolved
{
AddOrCreateMappers(Left, 0);
}

if (Right != null)
{
AddOrCreateMappers(Right, 1);
}

void AddOrCreateMappers(IEnumerable<IAssemblySymbol> elements, int index)
{
foreach (IAssemblySymbol assembly in elements)
{
if (!_assemblies.TryGetValue(assembly, out AssemblyMapper mapper))
{
mapper = new AssemblyMapper(Settings);
_assemblies.Add(assembly, mapper);
}
mapper.AddElement(assembly, index);
}
}
}

return _assemblies.Values;
}
}
}
Loading