Skip to content

Commit

Permalink
Merge pull request #16817 from safern/InitialApiCompat
Browse files Browse the repository at this point in the history
Add initial version of ApiCompatibility
  • Loading branch information
dsplaisted authored Apr 14, 2021
2 parents df26eb5 + c88e924 commit 37aaa12
Show file tree
Hide file tree
Showing 35 changed files with 2,730 additions and 1 deletion.
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
16 changes: 16 additions & 0 deletions sdk.sln
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,11 @@ 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}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Win32.Msi", "src\Microsoft.Win32.Msi\Microsoft.Win32.Msi.csproj", "{3D002392-6308-41DF-8BD5-224CCC5B049F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Win32.Msi.Tests", "src\Tests\Microsoft.Win32.Msi.Tests\Microsoft.Win32.Msi.Tests.csproj", "{80932949-B8B2-4163-B325-76F8FDBE3897}"
Expand Down Expand Up @@ -621,6 +626,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
{3D002392-6308-41DF-8BD5-224CCC5B049F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D002392-6308-41DF-8BD5-224CCC5B049F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D002392-6308-41DF-8BD5-224CCC5B049F}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -746,6 +759,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}
{3D002392-6308-41DF-8BD5-224CCC5B049F} = {22AB674F-ED91-4FBC-BFEE-8A1E82F9F05E}
{80932949-B8B2-4163-B325-76F8FDBE3897} = {580D1AE7-AA8F-4912-8B76-105594E00B3B}
{EEF4C7DD-CDC9-44B6-8B4F-725647D54ED8} = {580D1AE7-AA8F-4912-8B76-105594E00B3B}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// 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
{
/// <summary>
/// Class representing a difference of compatibility, containing detailed information about it.
/// </summary>
public class CompatDifference : IDiagnostic, IEquatable<CompatDifference>
{
/// <summary>
/// The Diagnostic ID for this difference.
/// </summary>
public string DiagnosticId { get; }

/// <summary>
/// The <see cref="DifferenceType"/>.
/// </summary>
public DifferenceType Type { get; }

/// <summary>
/// A diagnostic message for the difference.
/// </summary>
public virtual string Message { get; }

/// <summary>
/// A unique ID in order to identify the API that the difference was raised for.
/// </summary>
public string ReferenceId { get; }

private CompatDifference() { }

/// <summary>
/// Instantiate a new object representing the compatibility difference.
/// </summary>
/// <param name="id"><see cref="string"/> representing the diagnostic ID.</param>
/// <param name="message"><see cref="string"/> message describing the difference.</param>
/// <param name="type"><see cref="DifferenceType"/> to describe the type of the difference.</param>
/// <param name="member"><see cref="ISymbol"/> for which the difference is associated to.</param>
public CompatDifference(string diagnosticId, string message, DifferenceType type, ISymbol member)
: this(diagnosticId, message, type, member?.GetDocumentationCommentId())
{
}

/// <summary>
/// Instantiate a new object representing the compatibility difference.
/// </summary>
/// <param name="id"><see cref="string"/> representing the diagnostic ID.</param>
/// <param name="message"><see cref="string"/> message describing the difference.</param>
/// <param name="type"><see cref="DifferenceType"/> to describe the type of the difference.</param>
/// <param name="memberId"><see cref="string"/> containing the member ID for which the difference is associated to.</param>
public CompatDifference(string diagnosticId, string message, DifferenceType type, string memberId)
{
DiagnosticId = diagnosticId ?? throw new ArgumentNullException(nameof(diagnosticId));
Message = message ?? throw new ArgumentNullException(nameof(message));
Type = type;
ReferenceId = memberId ?? throw new ArgumentNullException(nameof(memberId));
}

/// <summary>
/// Evaluates whether the current object is equal to another <see cref="CompatDifference"/>.
/// </summary>
/// <param name="other"><see cref="CompatDifference"/> to compare against.</param>
/// <returns>True if equals, False if different.</returns>
public bool Equals(CompatDifference other) =>
other != null &&
Type == other.Type &&
DiagnosticId.Equals(other.DiagnosticId, StringComparison.OrdinalIgnoreCase) &&
ReferenceId.Equals(other.ReferenceId, StringComparison.OrdinalIgnoreCase) &&
Message.Equals(other.Message, StringComparison.OrdinalIgnoreCase);

/// <summary>
/// Gets the hashcode that reperesents this instance.
/// </summary>
/// <returns>Unique <see cref="int"/> based on the properties' values of the instance.</returns>
public override int GetHashCode() =>
HashCode.Combine(ReferenceId, DiagnosticId, Message, Type);

/// <summary>
/// Gets a <see cref="string"/> representation of the difference.
/// </summary>
/// <returns><see cref="string"/> describing the difference.</returns>
public override string ToString() => $"{DiagnosticId} : {Message}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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
{
/// <summary>
/// Enum representing the different type of differences available.
/// </summary>
public enum DifferenceType
{
Changed,
Added,
Removed
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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
{
/// <summary>
/// Provides a mechanism to filter <see cref="ISymbol"/> when building the <see cref="ElementMapper{T}"/>.
/// </summary>
public interface ISymbolFilter
{
/// <summary>
/// Determines whether the <see cref="ISymbol"/> should be included.
/// </summary>
/// <param name="symbol"><see cref="ISymbol"/> to evaluate.</param>
/// <returns>True to include the <paramref name="symbol"/> or false to filter it out.</returns>
bool Include(ISymbol symbol);
}
}
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 SymbolAccessibilityBasedFilter : ISymbolFilter
{
private readonly bool _includeInternalSymbols;

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

public bool Include(ISymbol symbol) =>
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,21 @@
// 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
{
/// <summary>
/// Interface that describes a diagnostic.
/// </summary>
public interface IDiagnostic
{
/// <summary>
/// String representing the diagnostic ID.
/// </summary>
string DiagnosticId { get; }

/// <summary>
/// String representing the ID for the object that the diagnostic was created for.
/// </summary>
string ReferenceId { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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.s

using System.Collections.Generic;

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
/// <summary>
/// Interface for rule drivers to implement in order to be used returned by the <see cref="IRuleRunnerFactory"/>
/// </summary>
public interface IRuleRunner
{
/// <summary>
/// Runs the registered rules on the mapper.
/// </summary>
/// <typeparam name="T">The underlying type on the mapper.</typeparam>
/// <param name="mapper">The mapper to run the rules on.</param>
/// <returns></returns>
IEnumerable<CompatDifference> Run<T>(ElementMapper<T> mapper);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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.s

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
/// <summary>
/// The factory to create the driver that the differ should use based on the <see cref="ComparingSettings"/>.
/// </summary>
public interface IRuleRunnerFactory
{
IRuleRunner GetRuleRunner();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 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 System;

namespace Microsoft.DotNet.ApiCompatibility.Abstractions
{
/// <summary>
/// Class that implements a visitor pattern to visit the tree for a given mapper.
/// </summary>
public class MapperVisitor
{
/// <summary>
/// Visits the tree for the given <see cref="ElementMapper{T}"/>.
/// </summary>
/// <typeparam name="T">Underlying type for the objects that the mapper holds.</typeparam>
/// <param name="mapper"><see cref="ElementMapper{T}"/> to visit.</param>
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);
}
}

/// <summary>
/// Visits the <see cref="AssemblySetMapper"/> and visits each <see cref="AssemblyMapper"/> in the mapper.
/// </summary>
/// <param name="mapper">The <see cref="AssemblySetMapper"/> to visit.</param>
public virtual void Visit(AssemblySetMapper mapper)
{
if (mapper == null)
{
throw new ArgumentNullException(nameof(mapper));
}

foreach (AssemblyMapper assembly in mapper.GetAssemblies())
{
Visit(assembly);
}
}

/// <summary>
/// Visits the <see cref="AssemblyMapper"/> and visits each <see cref="NamespaceMapper"/> in the mapper.
/// </summary>
/// <param name="mapper">The <see cref="AssemblyMapper"/> to visit.</param>
public virtual void Visit(AssemblyMapper mapper)
{
if (mapper == null)
{
throw new ArgumentNullException(nameof(mapper));
}

foreach (NamespaceMapper nsMapper in mapper.GetNamespaces())
{
Visit(nsMapper);
}
}

/// <summary>
/// Visits the <see cref="NamespaceMapper"/> and visits each <see cref="TypeMapper"/> in the mapper.
/// </summary>
/// <param name="mapper">The <see cref="NamespaceMapper"/> to visit.</param>
public virtual void Visit(NamespaceMapper mapper)
{
if (mapper == null)
{
throw new ArgumentNullException(nameof(mapper));
}

foreach (TypeMapper type in mapper.GetTypes())
{
Visit(type);
}
}

/// <summary>
/// Visits the <see cref="TypeMapper"/> and visits the nested types and members in the mapper.
/// </summary>
/// <param name="mapper">The <see cref="TypeMapper"/> to visit.</param>
public virtual void Visit(TypeMapper mapper)
{
if (mapper == null)
{
throw new ArgumentNullException(nameof(mapper));
}

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

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

/// <summary>
/// Visits the <see cref="MemberMapper"/>.
/// </summary>
/// <param name="mapper">The <see cref="MemberMapper"/> to visit.</param>
public virtual void Visit(MemberMapper mapper) { }
}
}
Loading

0 comments on commit 37aaa12

Please sign in to comment.