diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1313UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1313UnitTests.cs index 7787662b3..b5b667d31 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1313UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1313UnitTests.cs @@ -362,6 +362,118 @@ public override void Method(int Param1, int param2, int Other) await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); } + /// + /// This is a regression test for DotNetAnalyzers/StyleCopAnalyzers#1442: + /// https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1442 + /// + /// A representing the asynchronous operation. + [Fact] + public async Task TestSimpleLambaExpressionAsync() + { + var testCode = @"public class TypeName +{ + public void MethodName() + { + System.Action action = Ignored => { }; + } +}"; + + DiagnosticResult expected = this.CSharpDiagnostic().WithArguments("Ignored").WithLocation(5, 37); + + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// This is a regression test for DotNetAnalyzers/StyleCopAnalyzers#1343: + /// https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1343 + /// + /// A representing the asynchronous operation. + [Fact] + public async Task TestLambdaParameterNamedUnderscoreAsync() + { + var testCode = @"public class TypeName +{ + public void MethodName() + { + System.Action action1 = _ => { }; + System.Action action2 = (_) => { }; + System.Action action3 = delegate(int _) { }; + } +}"; + + await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// This is a regression test for DotNetAnalyzers/StyleCopAnalyzers#1343: + /// https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1343 + /// + /// + /// This diagnostic does not check whether or not a parameter named _ is being used. + /// + /// A representing the asynchronous operation. + [Fact] + public async Task TestLambdaParameterNamedUnderscoreUsageAsync() + { + var testCode = @"public class TypeName +{ + public void MethodName() + { + System.Func function1 = _ => _; + System.Func function2 = (_) => _; + System.Func function3 = delegate(int _) { return _; }; + } +}"; + + await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// This is a regression test for DotNetAnalyzers/StyleCopAnalyzers#1343: + /// https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1343 + /// + /// A representing the asynchronous operation. + [Fact] + public async Task TestLambdaParameterMultipleUnderscoresAsync() + { + var testCode = @"public class TypeName +{ + public void MethodName() + { + System.Action action1 = __ => { }; + System.Action action2 = (__) => { }; + System.Action action3 = delegate(int __) { }; + } +}"; + + DiagnosticResult[] expected = + { + this.CSharpDiagnostic().WithArguments("__").WithLocation(5, 38), + this.CSharpDiagnostic().WithArguments("__").WithLocation(6, 39), + this.CSharpDiagnostic().WithArguments("__").WithLocation(7, 51) + }; + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// This is a regression test for DotNetAnalyzers/StyleCopAnalyzers#1343: + /// https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1343 + /// + /// A representing the asynchronous operation. + [Fact] + public async Task TestMethodParameterNamedUnderscoreAsync() + { + var testCode = @"public class TypeName +{ + public void MethodName(int _) + { + } +}"; + + DiagnosticResult expected = this.CSharpDiagnostic().WithArguments("_").WithLocation(3, 32); + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + protected override IEnumerable GetCSharpDiagnosticAnalyzers() { yield return new SA1313ParameterNamesMustBeginWithLowerCaseLetter(); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1313ParameterNamesMustBeginWithLowerCaseLetter.cs b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1313ParameterNamesMustBeginWithLowerCaseLetter.cs index e80324fc0..09017ef63 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1313ParameterNamesMustBeginWithLowerCaseLetter.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1313ParameterNamesMustBeginWithLowerCaseLetter.cs @@ -16,6 +16,9 @@ namespace StyleCop.Analyzers.NamingRules /// /// A violation of this rule occurs when the name of a parameter does not begin with a lower-case letter. /// + /// An exception to this rule is made for lambda parameters named _. These parameters are often used to + /// designate a placeholder parameter which is not actually used in the body of the lambda expression. + /// /// If the parameter name is intended to match the name of an item associated with Win32 or COM, and thus /// needs to begin with an upper-case letter, place the parameter within a special NativeMethods class. A /// NativeMethods class is any class which contains a name ending in NativeMethods, and is intended as @@ -80,6 +83,11 @@ private static void HandleParameterSyntax(SyntaxNodeAnalysisContext context) return; } + if (name == "_" && IsInLambda(syntax)) + { + return; + } + if (NameMatchesAbstraction(syntax, context.SemanticModel)) { return; @@ -89,9 +97,31 @@ private static void HandleParameterSyntax(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(Diagnostic.Create(Descriptor, identifier.GetLocation(), name)); } + private static bool IsInLambda(ParameterSyntax syntax) + { + if (syntax.Parent.IsKind(SyntaxKind.SimpleLambdaExpression)) + { + return true; + } + + if (syntax.Parent.Parent.IsKind(SyntaxKind.ParenthesizedLambdaExpression) + || syntax.Parent.Parent.IsKind(SyntaxKind.AnonymousMethodExpression)) + { + return true; + } + + return false; + } + private static bool NameMatchesAbstraction(ParameterSyntax syntax, SemanticModel semanticModel) { - var parameterList = (ParameterListSyntax)syntax.Parent; + var parameterList = syntax.Parent as ParameterListSyntax; + if (parameterList == null) + { + // This occurs for simple lambda expressions (without parentheses) + return false; + } + var index = parameterList.Parameters.IndexOf(syntax); var declaringMember = syntax.Parent.Parent; diff --git a/documentation/SA1313.md b/documentation/SA1313.md index 11d0a5171..e6970f912 100644 --- a/documentation/SA1313.md +++ b/documentation/SA1313.md @@ -23,6 +23,9 @@ The name of a parameter in C# does not begin with a lower-case letter. A violation of this rule occurs when the name of a parameter does not begin with a lower-case letter. +An exception to this rule is made for lambda parameters named `_`. These parameters are often used to designate a +placeholder parameter which is not actually used in the body of the lambda expression. + If the parameter name is intended to match the name of an item associated with Win32 or COM, and thus needs to begin with an upper-case letter, place the parameter within a special `NativeMethods` class. A `NativeMethods` class is any class which contains a name ending in `NativeMethods`, and is intended as a placeholder for Win32 or COM wrappers.