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..484a745d5 --- /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( + SA1314ParametersShouldMatchInheritedNames.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[nameof(newNames)].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/SA1314UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1314UnitTests.cs new file mode 100644 index 000000000..1d2d361df --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1314UnitTests.cs @@ -0,0 +1,297 @@ +// 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 SA1314UnitTests : 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); + } + + protected override IEnumerable GetCSharpDiagnosticAnalyzers() + { + yield return new SA1314ParametersShouldMatchInheritedNames(); + } + + 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..04a039f6d 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..a970ed8de 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.Designer.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. // //------------------------------------------------------------------------------ @@ -14,12 +14,12 @@ namespace StyleCop.Analyzers.NamingRules { /// - /// A strongly-typed resource class, for looking up localized strings, etc. + /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. + // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert + // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. + // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen + // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] @@ -34,7 +34,7 @@ internal NamingResources() { } /// - /// Returns the cached ResourceManager instance used by this class. + /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { @@ -48,8 +48,8 @@ internal NamingResources() { } /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. + /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle + /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { @@ -62,7 +62,7 @@ internal NamingResources() { } /// - /// Looks up a localized string similar to Rename To '{0}'. + /// Sucht eine lokalisierte Zeichenfolge, die Rename To '{0}' ähnelt. /// internal static string RenameToCodeFix { get { @@ -71,7 +71,7 @@ internal static string RenameToCodeFix { } /// - /// Looks up a localized string similar to Prefix interface name with 'I'. + /// Sucht eine lokalisierte Zeichenfolge, die Prefix interface name with 'I' ähnelt. /// internal static string SA1302CodeFix { get { @@ -80,7 +80,7 @@ internal static string SA1302CodeFix { } /// - /// Looks up a localized string similar to The name of a variable in C# does not begin with a lower-case letter.. + /// Sucht eine lokalisierte Zeichenfolge, die The name of a variable in C# does not begin with a lower-case letter. ähnelt. /// internal static string SA1312Description { get { @@ -89,7 +89,7 @@ internal static string SA1312Description { } /// - /// Looks up a localized string similar to Variable '{0}' must begin with lower-case letter. + /// Sucht eine lokalisierte Zeichenfolge, die Variable '{0}' must begin with lower-case letter ähnelt. /// internal static string SA1312MessageFormat { get { @@ -98,7 +98,7 @@ internal static string SA1312MessageFormat { } /// - /// Looks up a localized string similar to Variable names must begin with lower-case letter. + /// Sucht eine lokalisierte Zeichenfolge, die Variable names must begin with lower-case letter ähnelt. /// internal static string SA1312Title { get { @@ -107,7 +107,7 @@ internal static string SA1312Title { } /// - /// Looks up a localized string similar to The name of a parameter in C# does not begin with a lower-case letter.. + /// Sucht eine lokalisierte Zeichenfolge, die The name of a parameter in C# does not begin with a lower-case letter. ähnelt. /// internal static string SA1313Description { get { @@ -116,7 +116,7 @@ internal static string SA1313Description { } /// - /// Looks up a localized string similar to Parameter '{0}' must begin with lower-case letter. + /// Sucht eine lokalisierte Zeichenfolge, die Parameter '{0}' must begin with lower-case letter ähnelt. /// internal static string SA1313MessageFormat { get { @@ -125,12 +125,39 @@ internal static string SA1313MessageFormat { } /// - /// Looks up a localized string similar to Parameter names must begin with lower-case letter. + /// Sucht eine lokalisierte Zeichenfolge, die Parameter names must begin with lower-case letter ähnelt. /// internal static string SA1313Title { get { return ResourceManager.GetString("SA1313Title", resourceCulture); } } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die 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. ähnelt. + /// + internal static string SA1314Description { + get { + return ResourceManager.GetString("SA1314Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Parameters should match inherited names ähnelt. + /// + internal static string SA1314MessageFormat { + get { + return ResourceManager.GetString("SA1314MessageFormat", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Parameters should match inherited names ähnelt. + /// + internal static string SA1314Title { + get { + return ResourceManager.GetString("SA1314Title", resourceCulture); + } + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.resx b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.resx index 3e857f5d3..115e5cbb7 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/SA1314ParametersShouldMatchInheritedNames.cs b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1314ParametersShouldMatchInheritedNames.cs new file mode 100644 index 000000000..9c2d17c1d --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1314ParametersShouldMatchInheritedNames.cs @@ -0,0 +1,120 @@ +// 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 SA1314ParametersShouldMatchInheritedNames : DiagnosticAnalyzer + { + /// + /// The ID for diagnostics produced by the analyzer. + /// + public const string DiagnosticId = "SA1314"; + private static readonly LocalizableString Title = new LocalizableResourceString(nameof(NamingResources.SA1314Title), NamingResources.ResourceManager, typeof(NamingResources)); + private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(NamingResources.SA1314MessageFormat), NamingResources.ResourceManager, typeof(NamingResources)); + private static readonly LocalizableString Description = new LocalizableResourceString(nameof(NamingResources.SA1314Description), NamingResources.ResourceManager, typeof(NamingResources)); + private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1314.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 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("newNames", 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..bdcc69655 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/StyleCop.Analyzers.csproj +++ b/StyleCop.Analyzers/StyleCop.Analyzers/StyleCop.Analyzers.csproj @@ -203,6 +203,7 @@ + diff --git a/StyleCopAnalyzers.sln b/StyleCopAnalyzers.sln index e44366a7f..ec937b062 100644 --- a/StyleCopAnalyzers.sln +++ b/StyleCopAnalyzers.sln @@ -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\SA1314.md = documentation\SA1314.md documentation\SA1400.md = documentation\SA1400.md documentation\SA1401.md = documentation\SA1401.md documentation\SA1402.md = documentation\SA1402.md diff --git a/documentation/SA1314.md b/documentation/SA1314.md new file mode 100644 index 000000000..011fe2b6c --- /dev/null +++ b/documentation/SA1314.md @@ -0,0 +1,40 @@ +## SA1314 + + + + + + + + + + + + + + +
TypeNameSA1314ParametersShouldMatchInheritedNames
CheckIdSA1314
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 begin with a lower-case letter. + +## 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 SA1314 // ParametersShouldMatchInheritedNames +public override void Method(int parameter) +#pragma warning restore SA1314 // ParametersShouldMatchInheritedNames +{ +} +```