| File: Rules\CannotAddOrRemoveVirtualKeyword.cs | Web Access |
| Project: src\sdk\src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompatibility\Microsoft.DotNet.ApiCompatibility.csproj (Microsoft.DotNet.ApiCompatibility) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.CodeAnalysis; using Microsoft.DotNet.ApiSymbolExtensions; namespace Microsoft.DotNet.ApiCompatibility.Rules { /// <summary> /// This class implements a rule to check that the 'virtual' keyword is not added to /// or removed from a member. /// </summary> public class CannotAddOrRemoveVirtualKeyword : IRule { private readonly IRuleSettings _settings; private static bool IsSealed(ISymbol sym) => sym.IsSealed || (!sym.IsVirtual && !sym.IsAbstract); public CannotAddOrRemoveVirtualKeyword(IRuleSettings settings, IRuleRegistrationContext context) { _settings = settings; context.RegisterOnMemberSymbolAction(RunOnMemberSymbol); } private void RunOnMemberSymbol(ISymbol? left, ISymbol? right, ITypeSymbol leftContainingType, ITypeSymbol rightContainingType, MetadataInformation leftMetadata, MetadataInformation rightMetadata, IList<CompatDifference> differences) { // Members must exist if (left is null || right is null) { return; } if (leftContainingType.TypeKind == TypeKind.Interface || rightContainingType.TypeKind == TypeKind.Interface) { if (!IsSealed(left) && IsSealed(right)) { // Introducing the sealed keyword to an interface method is a breaking change. differences.Add(new CompatDifference( leftMetadata, rightMetadata, DiagnosticIds.CannotAddSealedToInterfaceMember, string.Format(Resources.CannotAddSealedToInterfaceMember, right), DifferenceType.Added, right)); } return; } if (left.IsVirtual) { // Removing the virtual keyword from a member in a sealed type won't be a breaking change. if (leftContainingType.IsEffectivelySealed(_settings.IncludeInternalSymbols)) { return; } // If left is virtual and right is not, then emit a diagnostic // specifying that the virtual modifier cannot be removed. if (!right.IsVirtual) { differences.Add(new CompatDifference( leftMetadata, rightMetadata, DiagnosticIds.CannotRemoveVirtualFromMember, string.Format(Resources.CannotRemoveVirtualOrAbstractFromMember, "virtual", left), DifferenceType.Removed, right)); } } // If the left member is not virtual, ensure that we're in strict mode. // Adding virtual to a member is only flagged in strict mode as it's not a binary breaking change, // though it may break source compatibility in some scenarios (e.g., when overriding methods with covariant return types). else if (_settings.StrictMode) { // If the right member is virtual, emit a diagnostic // that the virtual modifier cannot be added. if (right.IsVirtual) { differences.Add(new CompatDifference( leftMetadata, rightMetadata, DiagnosticIds.CannotAddVirtualToMember, string.Format(Resources.CannotAddVirtualToMember, right), DifferenceType.Added, right)); } } if (left.IsAbstract) { if (!right.IsAbstract && !right.IsVirtual) { // abstract can be made virtual but cannot remove abstract. differences.Add(new CompatDifference( leftMetadata, rightMetadata, DiagnosticIds.CannotRemoveVirtualFromMember, string.Format(Resources.CannotRemoveVirtualOrAbstractFromMember, "abstract", left), DifferenceType.Removed, right)); } } } } }