From 6484022f6ef83bf37d65c0c1ad42ca5c7b3abf06 Mon Sep 17 00:00:00 2001 From: Dennis Fischer Date: Tue, 29 Dec 2015 01:32:57 +0100 Subject: [PATCH] Implement SA1315 --- .../NamingRules/RenameToAnyCodeFixProvider.cs | 82 ++++ .../StyleCop.Analyzers.CodeFixes.csproj | 1 + .../NamingRules/SA1315UnitTests.cs | 387 ++++++++++++++++++ .../StyleCop.Analyzers.Test.csproj | 1 + .../Helpers/NamedTypeHelpers.cs | 21 + .../NamingRules/NamingResources.Designer.cs | 27 ++ .../NamingRules/NamingResources.resx | 9 + ...1315ParametersShouldMatchInheritedNames.cs | 133 ++++++ .../StyleCop.Analyzers.csproj | 2 + StyleCopAnalyzers.sln | 3 +- documentation/SA1315.md | 40 ++ 11 files changed, 705 insertions(+), 1 deletion(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/NamingRules/RenameToAnyCodeFixProvider.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1315UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1315ParametersShouldMatchInheritedNames.cs create mode 100644 documentation/SA1315.md diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/NamingRules/RenameToAnyCodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/NamingRules/RenameToAnyCodeFixProvider.cs new file mode 100644 index 000000000..93c1cbab4 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/NamingRules/RenameToAnyCodeFixProvider.cs @@ -0,0 +1,82 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.NamingRules +{ + using System.Collections.Immutable; + using System.Composition; + using System.Threading.Tasks; + using Helpers; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CodeActions; + using Microsoft.CodeAnalysis.CodeFixes; + using Microsoft.CodeAnalysis.CSharp; + + /// + /// Implements a code fix for diagnostics which are fixed by renaming a symbol to a set of given new names. + /// + /// + /// To fix a violation of this rule, change the name of the symbol so that it is one of the set of new names. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RenameToAnyCodeFixProvider))] + [Shared] + internal class RenameToAnyCodeFixProvider : CodeFixProvider + { + private static char[] comma = { ',' }; + + /// + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create( + SA1315ParametersShouldMatchInheritedNames.DiagnosticId); + + /// + public override FixAllProvider GetFixAllProvider() + { + return CustomFixAllProviders.BatchFixer; + } + + /// + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var document = context.Document; + var root = await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + foreach (var diagnostic in context.Diagnostics) + { + var token = root.FindToken(diagnostic.Location.SourceSpan.Start); + if (string.IsNullOrEmpty(token.ValueText)) + { + continue; + } + + var originalName = token.ValueText; + + var memberSyntax = RenameHelper.GetParentDeclaration(token); + + SemanticModel semanticModel = await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + + var declaredSymbol = semanticModel.GetDeclaredSymbol(memberSyntax); + if (declaredSymbol == null) + { + continue; + } + + string[] newNames = diagnostic.Properties[SA1315ParametersShouldMatchInheritedNames.PropertyName].Split(comma); + foreach (var newName in newNames) + { + if (!await RenameHelper.IsValidNewMemberNameAsync(semanticModel, declaredSymbol, newName, context.CancellationToken).ConfigureAwait(false)) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + string.Format(NamingResources.RenameToCodeFix, newName), + cancellationToken => RenameHelper.RenameSymbolAsync(document, root, token, newName, cancellationToken), + nameof(RenameToAnyCodeFixProvider) + newName), + diagnostic); + } + } + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.CodeFixes.csproj b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.CodeFixes.csproj index 34f5504ae..23bab45b2 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.CodeFixes.csproj +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.CodeFixes.csproj @@ -96,6 +96,7 @@ + diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1315UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1315UnitTests.cs new file mode 100644 index 000000000..07f1aeecb --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1315UnitTests.cs @@ -0,0 +1,387 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.NamingRules +{ + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.CodeFixes; + using Microsoft.CodeAnalysis.Diagnostics; + using StyleCop.Analyzers.NamingRules; + using TestHelper; + using Xunit; + using Microsoft.CodeAnalysis; + public class SA1315UnitTests : CodeFixVerifier + { + [Fact] + public async Task TestThatDiagnosticIsNotReportedForMethodWithoutArgumentsAsync() + { + var testCode = @"public class TypeName +{ + public void AMethod() { } +}"; + + await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatDiagnosticIsNotReportedForMethodWithoutInheritNamesAsync() + { + var testCode = @"public class TypeName +{ + public void AMethod(string arg1, string arg2) { } +}"; + + await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatDiagnosticIsNotReportedForMethodWithoutInheritNamesAndArglistAsync() + { + var testCode = @"public class TypeName +{ + public void AMethod(string arg1, string arg2, __arglist) { } +}"; + + await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatDiagnosticIsNotReportedForMethodWithoutInheritNamesAnParamsAsync() + { + var testCode = @"public class TypeName +{ + public void AMethod(string arg1, string arg2, params string[] arg3) { } +}"; + + await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatDiagnosticIsReportedForMethodWithInheritNamesAsync() + { + var testCode = @"public class TypeName : BaseClass +{ + public override void AMethod(string arg1, string arg2) { } +} + +public abstract class BaseClass +{ + public abstract void AMethod(string baseArg1, string baseArg2); +}"; + + var fixedCode = @"public class TypeName : BaseClass +{ + public override void AMethod(string baseArg1, string baseArg2) { } +} + +public abstract class BaseClass +{ + public abstract void AMethod(string baseArg1, string baseArg2); +}"; + + var expected = new[] + { + this.CSharpDiagnostic().WithLocation(3, 41), + this.CSharpDiagnostic().WithLocation(3, 54) + }; + + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatDiagnosticIsReportedForMethodWithInheritNamesAndArglistAsync() + { + var testCode = @"public class TypeName : BaseClass +{ + public override void AMethod(string arg1, string arg2, __arglist) { } +} + +public abstract class BaseClass +{ + public abstract void AMethod(string baseArg1, string baseArg2, __arglist); +}"; + + var fixedCode = @"public class TypeName : BaseClass +{ + public override void AMethod(string baseArg1, string baseArg2, __arglist) { } +} + +public abstract class BaseClass +{ + public abstract void AMethod(string baseArg1, string baseArg2, __arglist); +}"; + + var expected = new[] + { + this.CSharpDiagnostic().WithLocation(3, 41), + this.CSharpDiagnostic().WithLocation(3, 54) + }; + + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatDiagnosticIsReportedForMethodWithInheritNamesAnParamsAsync() + { + var testCode = @"public class TypeName : BaseClass +{ + public override void AMethod(string arg1, string arg2, params string[] arg3) { } +} + +public abstract class BaseClass +{ + public abstract void AMethod(string baseArg1, string baseArg2, params string[] baseArg3); +}"; + + var fixedCode = @"public class TypeName : BaseClass +{ + public override void AMethod(string baseArg1, string baseArg2, params string[] baseArg3) { } +} + +public abstract class BaseClass +{ + public abstract void AMethod(string baseArg1, string baseArg2, params string[] baseArg3); +}"; + + var expected = new[] + { + this.CSharpDiagnostic().WithLocation(3, 41), + this.CSharpDiagnostic().WithLocation(3, 54), + this.CSharpDiagnostic().WithLocation(3, 76) + }; + + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 3).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatDiagnosticIsReportedForMethodWithInheritNamesWithInterfaceAsync() + { + var testCode = @"public class TypeName : IBase +{ + public void AMethod(string arg1, string arg2) { } +} + +public interface IBase +{ + void AMethod(string baseArg1, string baseArg2); +}"; + + var fixedCode = @"public class TypeName : IBase +{ + public void AMethod(string baseArg1, string baseArg2) { } +} + +public interface IBase +{ + void AMethod(string baseArg1, string baseArg2); +}"; + + var expected = new[] + { + this.CSharpDiagnostic().WithLocation(3, 32), + this.CSharpDiagnostic().WithLocation(3, 45) + }; + + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatDiagnosticIsReportedForMethodWithInheritNamesAndArglistWithInterfaceAsync() + { + var testCode = @"public class TypeName : IBase +{ + public void AMethod(string arg1, string arg2, __arglist) { } +} + +public interface IBase +{ + void AMethod(string baseArg1, string baseArg2, __arglist); +}"; + + var fixedCode = @"public class TypeName : IBase +{ + public void AMethod(string baseArg1, string baseArg2, __arglist) { } +} + +public interface IBase +{ + void AMethod(string baseArg1, string baseArg2, __arglist); +}"; + + var expected = new[] + { + this.CSharpDiagnostic().WithLocation(3, 32), + this.CSharpDiagnostic().WithLocation(3, 45) + }; + + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatDiagnosticIsReportedForMethodWithInheritNamesAnParamsWithInterfaceAsync() + { + var testCode = @"public class TypeName : IBase +{ + public void AMethod(string arg1, string arg2, params string[] arg3) { } +} + +public interface IBase +{ + void AMethod(string baseArg1, string baseArg2, params string[] baseArg3); +}"; + + var fixedCode = @"public class TypeName : IBase +{ + public void AMethod(string baseArg1, string baseArg2, params string[] baseArg3) { } +} + +public interface IBase +{ + void AMethod(string baseArg1, string baseArg2, params string[] baseArg3); +}"; + + var expected = new[] + { + this.CSharpDiagnostic().WithLocation(3, 32), + this.CSharpDiagnostic().WithLocation(3, 45), + this.CSharpDiagnostic().WithLocation(3, 67) + }; + + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 3).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatDiagnosticIsNotReportedForInvalidOverrideAsync() + { + var testCode = @"public class TypeName +{ + public override void AMethod(string arg1, string arg2, params string[] arg3) { } +}"; + + var expected = + new DiagnosticResult + { + Id = "CS0115", + Severity = DiagnosticSeverity.Error, + Message = "'TypeName.AMethod(string, string, params string[])': no suitable method found to override", + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 3, 26) } + }; + + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatDiagnosticIsReportedForLongerInheritageChainAsync() + { + var testCode = @"public class TopLevelBaseClass +{ + public virtual void Method(int baseArg) { } +} + +public class IntermediateBaseClass : TopLevelBaseClass +{ +} + +public class TestClass : IntermediateBaseClass +{ + public override void Method(int arg) { } +}"; + + var fixedCode = @"public class TopLevelBaseClass +{ + public virtual void Method(int baseArg) { } +} + +public class IntermediateBaseClass : TopLevelBaseClass +{ +} + +public class TestClass : IntermediateBaseClass +{ + public override void Method(int baseArg) { } +}"; + + var expected = new[] + { + this.CSharpDiagnostic().WithLocation(12, 35) + }; + + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 1).ConfigureAwait(false); + } + + [Fact] + public async Task TestThatRenameToBaseClassTakesPriorityAsync() + { + var testCode = @"interface IInterface +{ + void Method(string p1); +} + +abstract class BaseClass +{ + public abstract void Method(string p2); +} + +class Derived : BaseClass, IInterface +{ + public override void Method(string p) + { + } +}"; + + var fixedCode = @"interface IInterface +{ + void Method(string p1); +} + +abstract class BaseClass +{ + public abstract void Method(string p2); +} + +class Derived : BaseClass, IInterface +{ + public override void Method(string p2) + { + } +}"; + + var expected = new[] + { + this.CSharpDiagnostic().WithLocation(13, 38) + }; + + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + + Assert.Equal(1, (await this.GetOfferedCSharpFixesAsync(testCode).ConfigureAwait(false)).Length); + await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 1).ConfigureAwait(false); + } + + protected override IEnumerable GetCSharpDiagnosticAnalyzers() + { + yield return new SA1315ParametersShouldMatchInheritedNames(); + } + + protected override CodeFixProvider GetCSharpCodeFixProvider() + { + return new RenameToAnyCodeFixProvider(); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj b/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj index 9f69ba3fe..c03640d04 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj @@ -265,6 +265,7 @@ + diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/NamedTypeHelpers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/NamedTypeHelpers.cs index 56050aec6..2c4b30f84 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/NamedTypeHelpers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/NamedTypeHelpers.cs @@ -4,6 +4,7 @@ namespace StyleCop.Analyzers.Helpers { using System; + using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -151,5 +152,25 @@ internal static bool IsImplementingAnInterfaceMember(ISymbol memberSymbol) .Select(typeSymbol.FindImplementationForInterfaceMember) .Any(x => memberSymbol.Equals(x)); } + + internal static void GetOriginalDefinitions(List originalDefinitions, IMethodSymbol memberSymbol) + { + if (memberSymbol.IsOverride && memberSymbol.OverriddenMethod != null) + { + originalDefinitions.Add(memberSymbol.OverriddenMethod); + } + + if (memberSymbol.ExplicitInterfaceImplementations.Length > 0) + { + originalDefinitions.AddRange(memberSymbol.ExplicitInterfaceImplementations); + } + + var typeSymbol = memberSymbol.ContainingType; + + originalDefinitions.AddRange(typeSymbol.AllInterfaces + .SelectMany(m => m.GetMembers(memberSymbol.Name)) + .Where(m => typeSymbol.FindImplementationForInterfaceMember(m) != null) + .Cast()); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.Designer.cs b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.Designer.cs index fb46a7fff..68729d9c7 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.Designer.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.Designer.cs @@ -132,5 +132,32 @@ internal static string SA1313Title { return ResourceManager.GetString("SA1313Title", resourceCulture); } } + + /// + /// Looks up a localized string similar to When a method overrides a method from a base class, or implements an interface method, the parameter names of the overriding method should match the names in the base definition.. + /// + internal static string SA1315Description { + get { + return ResourceManager.GetString("SA1315Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameters should match inherited names. + /// + internal static string SA1315MessageFormat { + get { + return ResourceManager.GetString("SA1315MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameters should match inherited names. + /// + internal static string SA1315Title { + get { + return ResourceManager.GetString("SA1315Title", resourceCulture); + } + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.resx b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.resx index 3e857f5d3..834aa37a7 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.resx +++ b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.resx @@ -141,4 +141,13 @@ Parameter names must begin with lower-case letter + + When a method overrides a method from a base class, or implements an interface method, the parameter names of the overriding method should match the names in the base definition. + + + Parameters should match inherited names + + + Parameters should match inherited names + \ No newline at end of file diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1315ParametersShouldMatchInheritedNames.cs b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1315ParametersShouldMatchInheritedNames.cs new file mode 100644 index 000000000..c2e3cdd95 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1315ParametersShouldMatchInheritedNames.cs @@ -0,0 +1,133 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.NamingRules +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + using Helpers; + using Helpers.ObjectPools; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; + + /// + /// Parameters should match inherited names + /// + /// + /// A violation of this rule occurs when the name of a parameter does not match its inherited name. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal class SA1315ParametersShouldMatchInheritedNames : DiagnosticAnalyzer + { + /// + /// The ID for diagnostics produced by the analyzer. + /// + public const string DiagnosticId = "SA1315"; + internal const string PropertyName = "NewNames"; + private static readonly LocalizableString Title = new LocalizableResourceString(nameof(NamingResources.SA1315Title), NamingResources.ResourceManager, typeof(NamingResources)); + private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(NamingResources.SA1315MessageFormat), NamingResources.ResourceManager, typeof(NamingResources)); + private static readonly LocalizableString Description = new LocalizableResourceString(nameof(NamingResources.SA1315Description), NamingResources.ResourceManager, typeof(NamingResources)); + private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1315.md"; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.NamingRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink); + + private static readonly Action CompilationStartAction = HandleCompilationStart; + private static readonly Action HandleMethodAction = HandleMethod; + + /// + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(Descriptor); + + /// + public override void Initialize(AnalysisContext context) + { + context.RegisterCompilationStartAction(CompilationStartAction); + } + + private static void HandleCompilationStart(CompilationStartAnalysisContext context) + { + context.RegisterSymbolAction(HandleMethodAction, SymbolKind.Method); + } + + private static void HandleMethod(SymbolAnalysisContext context) + { + var symbol = context.Symbol as IMethodSymbol; + + if (!symbol.IsOverride && !NamedTypeHelpers.IsImplementingAnInterfaceMember(symbol)) + { + return; + } + + if (!symbol.CanBeReferencedByName + || !symbol.Locations.Any(x => x.IsInSource) + || string.IsNullOrWhiteSpace(symbol.Name)) + { + return; + } + + var pool = SharedPools.Default>(); + + using (var pooledObject = pool.GetPooledObject()) + { + var originalDefinitions = pooledObject.Object; + NamedTypeHelpers.GetOriginalDefinitions(originalDefinitions, symbol); + + if (originalDefinitions.Count == 0) + { + // We did not find any original definitions so we don't have to do anything. This happens if the method has an override modifier + // but does not have any valid method it is overriding. + return; + } + + for (int i = 0; i < symbol.Parameters.Length; i++) + { + var currentParameter = symbol.Parameters[i]; + bool foundMatch = false; + foreach (var originalDefinition in originalDefinitions) + { + var originalParameter = originalDefinition.Parameters[i]; + + if (currentParameter.Name == originalParameter.Name) + { + foundMatch = true; + break; + } + } + + if (!foundMatch) + { + + var baseClassMethod = originalDefinitions.FirstOrDefault(x => x.ContainingType.TypeKind != TypeKind.Interface); + if (baseClassMethod != null) + { + // If there is a base class with a matching method declaration prefer it + var properties = ImmutableDictionary.Empty.SetItem(PropertyName, baseClassMethod.Parameters[i].Name); + + context.ReportDiagnostic(Diagnostic.Create(Descriptor, currentParameter.Locations.First(), properties, null)); + } + else + { + var resultBuilder = StringBuilderPool.Allocate(); + + // originalDefinitions must have at least one entry + resultBuilder.Append(originalDefinitions[0].Parameters[i].Name); + + for (int j = 1; j < originalDefinitions.Count; j++) + { + resultBuilder.Append(','); + resultBuilder.Append(originalDefinitions[j].Parameters[i].Name); + } + + var properties = ImmutableDictionary.Empty.SetItem(PropertyName, StringBuilderPool.ReturnAndFree(resultBuilder)); + + context.ReportDiagnostic(Diagnostic.Create(Descriptor, currentParameter.Locations.First(), properties, null)); + } + } + } + } + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/StyleCop.Analyzers.csproj b/StyleCop.Analyzers/StyleCop.Analyzers/StyleCop.Analyzers.csproj index 8b0c1d8ba..0e4959388 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/StyleCop.Analyzers.csproj +++ b/StyleCop.Analyzers/StyleCop.Analyzers/StyleCop.Analyzers.csproj @@ -203,6 +203,7 @@ + @@ -412,6 +413,7 @@ ResXFileCodeGenerator NamingResources.Designer.cs + Designer ResXFileCodeGenerator diff --git a/StyleCopAnalyzers.sln b/StyleCopAnalyzers.sln index e44366a7f..9e80af54a 100644 --- a/StyleCopAnalyzers.sln +++ b/StyleCopAnalyzers.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StyleCop.Analyzers", "StyleCop.Analyzers\StyleCop.Analyzers\StyleCop.Analyzers.csproj", "{3B052737-06CE-4182-AE0F-08EB82DFA73E}" EndProject @@ -133,6 +133,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "documentation", "documentat documentation\SA1311.md = documentation\SA1311.md documentation\SA1312.md = documentation\SA1312.md documentation\SA1313.md = documentation\SA1313.md + documentation\SA1315.md = documentation\SA1315.md documentation\SA1400.md = documentation\SA1400.md documentation\SA1401.md = documentation\SA1401.md documentation\SA1402.md = documentation\SA1402.md diff --git a/documentation/SA1315.md b/documentation/SA1315.md new file mode 100644 index 000000000..bdebf6876 --- /dev/null +++ b/documentation/SA1315.md @@ -0,0 +1,40 @@ +## SA1315 + + + + + + + + + + + + + + +
TypeNameSA1315ParametersShouldMatchInheritedNames
CheckIdSA1315
CategoryNaming Rules
+ +:memo: This rule is new for StyleCop Analyzers, and was not present in StyleCop Classic. + +## Cause + +A violation of this rule occurs when the name of a parameter does not match its inherited name. + +## Rule description + +A violation of this rule occurs when the name of a parameter does not match its inherited name. + +## How to fix violations + +To fix a violation of this rule, change the name of the parameter so that it matches its inherited name. + +## How to suppress violations + +```csharp +#pragma warning disable SA1315 // ParametersShouldMatchInheritedNames +public override void Method(int parameter) +#pragma warning restore SA1315 // ParametersShouldMatchInheritedNames +{ +} +```