diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/ReadabilityRules/SA1111CSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/ReadabilityRules/SA1111CSharp11UnitTests.cs index 7ab5725da..9d7093990 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/ReadabilityRules/SA1111CSharp11UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/ReadabilityRules/SA1111CSharp11UnitTests.cs @@ -3,9 +3,20 @@ namespace StyleCop.Analyzers.Test.CSharp11.ReadabilityRules { + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp10.ReadabilityRules; + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1111ClosingParenthesisMustBeOnLineOfLastParameter, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; public partial class SA1111CSharp11UnitTests : SA1111CSharp10UnitTests { + protected override DiagnosticResult[] GetExpectedResultTestPrimaryConstructorWithParameter() + { + return new[] + { + Diagnostic().WithLocation(0), + }; + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1111CSharp9UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1111CSharp9UnitTests.cs index e7e70f249..5e0b146ff 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1111CSharp9UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1111CSharp9UnitTests.cs @@ -3,9 +3,116 @@ namespace StyleCop.Analyzers.Test.CSharp9.ReadabilityRules { + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp8.ReadabilityRules; + using StyleCop.Analyzers.Test.Helpers; + using Xunit; + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1111ClosingParenthesisMustBeOnLineOfLastParameter, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; public partial class SA1111CSharp9UnitTests : SA1111CSharp8UnitTests { + [Theory] + [MemberData(nameof(CommonMemberData.TypeKeywordsWhichSupportPrimaryConstructors), MemberType = typeof(CommonMemberData))] + [WorkItem(3785, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3785")] + public async Task TestPrimaryConstructorWithParameterAsync(string typeKeyword) + { + var testCode = $@" +{typeKeyword} Foo(int x + {{|#0:)|}} +{{ +}}"; + + var fixedCode = $@" +{typeKeyword} Foo(int x) +{{ +}}"; + + var expected = this.GetExpectedResultTestPrimaryConstructorWithParameter(); + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(CommonMemberData.TypeKeywordsWhichSupportPrimaryConstructors), MemberType = typeof(CommonMemberData))] + [WorkItem(3785, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3785")] + public async Task TestPrimaryConstructorWithoutParameterAsync(string typeKeyword) + { + var testCode = $@" +{typeKeyword} Foo( + ) +{{ +}}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(CommonMemberData.ReferenceTypeKeywordsWhichSupportPrimaryConstructors), MemberType = typeof(CommonMemberData))] + [WorkItem(3785, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3785")] + public async Task TestPrimaryConstructorBaseListWithArgumentsAsync(string typeKeyword) + { + var testCode = $@" +{typeKeyword} Foo(int x) +{{ +}} + +{typeKeyword} Bar(int x) : Foo(x + {{|#0:)|}} +{{ +}}"; + + var fixedCode = $@" +{typeKeyword} Foo(int x) +{{ +}} + +{typeKeyword} Bar(int x) : Foo(x) +{{ +}}"; + + var expected = this.GetExpectedResultTestPrimaryConstructorBaseList(); + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } + + [Theory] + [MemberData(nameof(CommonMemberData.ReferenceTypeKeywordsWhichSupportPrimaryConstructors), MemberType = typeof(CommonMemberData))] + [WorkItem(3785, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3785")] + public async Task TestPrimaryConstructorBaseListWithoutArgumentsAsync(string typeKeyword) + { + var testCode = $@" +{typeKeyword} Foo() +{{ +}} + +{typeKeyword} Bar(int x) : Foo( + ) +{{ +}}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + protected virtual DiagnosticResult[] GetExpectedResultTestPrimaryConstructorWithParameter() + { + return new[] + { + // Diagnostic issued twice because of https://github.com/dotnet/roslyn/issues/53136 + Diagnostic().WithLocation(0), + Diagnostic().WithLocation(0), + }; + } + + protected virtual DiagnosticResult[] GetExpectedResultTestPrimaryConstructorBaseList() + { + return new[] + { + // Diagnostic issued twice because of https://github.com/dotnet/roslyn/issues/70488 + Diagnostic().WithLocation(0), + Diagnostic().WithLocation(0), + }; + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1612UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1612UnitTests.cs index e48f37635..450299a6f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1612UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1612UnitTests.cs @@ -536,13 +536,13 @@ public class ClassName } private static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult expected, CancellationToken cancellationToken) - => VerifyCSharpDiagnosticAsync(source, testSettings: null, new[] { expected }, false, cancellationToken); + => VerifyCSharpDiagnosticAsync(source, testSettings: null, new[] { expected }, ignoreCompilerDiagnostics: false, cancellationToken); private static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken) - => VerifyCSharpDiagnosticAsync(source, testSettings: null, expected, false, cancellationToken); + => VerifyCSharpDiagnosticAsync(source, testSettings: null, expected, ignoreCompilerDiagnostics: false, cancellationToken); private static Task VerifyCSharpDiagnosticAsync(string source, string testSettings, DiagnosticResult[] expected, CancellationToken cancellationToken) - => VerifyCSharpDiagnosticAsync(source, testSettings, expected, false, cancellationToken); + => VerifyCSharpDiagnosticAsync(source, testSettings, expected, ignoreCompilerDiagnostics: false, cancellationToken); private static Task VerifyCSharpDiagnosticAsync(string source, string testSettings, DiagnosticResult[] expected, bool ignoreCompilerDiagnostics, CancellationToken cancellationToken) { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs index 6ab2508c5..1c45948b0 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs @@ -1,8 +1,6 @@ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -#nullable disable - namespace StyleCop.Analyzers.Test.Helpers { using System.Collections.Generic; @@ -115,7 +113,7 @@ public static IEnumerable GenericTypeDeclarationKeywords } } - public static IEnumerable TypeKeywordsWhichSupportPrimaryConstructors + public static IEnumerable ReferenceTypeKeywordsWhichSupportPrimaryConstructors { get { @@ -127,22 +125,33 @@ public static IEnumerable TypeKeywordsWhichSupportPrimaryConstructors if (LightupHelpers.SupportsCSharp10) { yield return new[] { "record class" }; - yield return new[] { "record struct" }; } if (LightupHelpers.SupportsCSharp12) { yield return new[] { "class" }; - yield return new[] { "struct" }; } } } - public static IEnumerable ReferenceTypeKeywordsWhichSupportPrimaryConstructors + public static IEnumerable TypeKeywordsWhichSupportPrimaryConstructors { get { - return TypeKeywordsWhichSupportPrimaryConstructors.Where(x => !((string)x[0]).Contains("struct")); + foreach (var keyword in ReferenceTypeKeywordsWhichSupportPrimaryConstructors) + { + yield return keyword; + } + + if (LightupHelpers.SupportsCSharp10) + { + yield return new[] { "record struct" }; + } + + if (LightupHelpers.SupportsCSharp12) + { + yield return new[] { "struct" }; + } } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs index a965afcba..d348346ec 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs @@ -60,6 +60,8 @@ internal class SA1111ClosingParenthesisMustBeOnLineOfLastParameter : DiagnosticA SyntaxKind.OperatorDeclaration, SyntaxKind.ConversionOperatorDeclaration); + private static readonly Action TypeDeclarationAction = HandleTypeDeclaration; + private static readonly Action PrimaryConstructorBaseTypeAction = HandlePrimaryConstructorBaseType; private static readonly Action BaseMethodDeclarationAction = HandleBaseMethodDeclaration; private static readonly Action LocalFunctionStatementAction = HandleLocalFunctionStatement; private static readonly Action InvocationExpressionAction = HandleInvocationExpression; @@ -82,6 +84,8 @@ public override void Initialize(AnalysisContext context) context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(TypeDeclarationAction, SyntaxKinds.TypeDeclaration); + context.RegisterSyntaxNodeAction(PrimaryConstructorBaseTypeAction, SyntaxKindEx.PrimaryConstructorBaseType); context.RegisterSyntaxNodeAction(BaseMethodDeclarationAction, HandledMethodSyntaxKinds); context.RegisterSyntaxNodeAction(LocalFunctionStatementAction, SyntaxKindEx.LocalFunctionStatement); context.RegisterSyntaxNodeAction(InvocationExpressionAction, SyntaxKind.InvocationExpression); @@ -111,250 +115,151 @@ private static void HandleArrayCreationExpression(SyntaxNodeAnalysisContext cont continue; } - var lastSize = arrayRankSpecifierSyntax.Sizes - .Last(); + var lastSize = arrayRankSpecifierSyntax.Sizes.Last(); - if (!arrayRankSpecifierSyntax.CloseBracketToken.IsMissing) - { - CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( - context, - lastSize, - arrayRankSpecifierSyntax.CloseBracketToken); - } + CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( + context, + lastSize, + arrayRankSpecifierSyntax.CloseBracketToken); } } private static void HandleParenthesizedLambdaExpression(SyntaxNodeAnalysisContext context) { var lambdaExpressionSyntax = (ParenthesizedLambdaExpressionSyntax)context.Node; - - if (lambdaExpressionSyntax.ParameterList == null || - lambdaExpressionSyntax.ParameterList.IsMissing || - !lambdaExpressionSyntax.ParameterList.Parameters.Any()) - { - return; - } - - var lastParameter = lambdaExpressionSyntax.ParameterList - .Parameters - .Last(); - - if (!lambdaExpressionSyntax.ParameterList.CloseParenToken.IsMissing) - { - CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( - context, - lastParameter, - lambdaExpressionSyntax.ParameterList.CloseParenToken); - } + CheckParameterList(context, lambdaExpressionSyntax.ParameterList); } private static void HandleAnonymousMethodExpression(SyntaxNodeAnalysisContext context) { var anonymousMethod = (AnonymousMethodExpressionSyntax)context.Node; - - if (anonymousMethod.ParameterList == null || - anonymousMethod.ParameterList.IsMissing || - !anonymousMethod.ParameterList.Parameters.Any()) - { - return; - } - - var lastParameter = anonymousMethod.ParameterList - .Parameters - .Last(); - - if (!anonymousMethod.ParameterList.CloseParenToken.IsMissing) - { - CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( - context, - lastParameter, - anonymousMethod.ParameterList.CloseParenToken); - } + CheckParameterList(context, anonymousMethod.ParameterList); } private static void HandleAttribute(SyntaxNodeAnalysisContext context) { var attribute = (AttributeSyntax)context.Node; + var argumentList = attribute.ArgumentList; - if (attribute.ArgumentList == null || - attribute.ArgumentList.IsMissing || - !attribute.ArgumentList.Arguments.Any()) + if (argumentList == null || argumentList.IsMissing || !argumentList.Arguments.Any()) { return; } - var lastArgument = attribute.ArgumentList - .Arguments - .Last(); + var lastParameter = argumentList.Arguments.Last(); - if (!attribute.ArgumentList.CloseParenToken.IsMissing) - { - CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( - context, - lastArgument, - attribute.ArgumentList.CloseParenToken); - } + CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( + context, + lastParameter, + argumentList.CloseParenToken); } private static void HandleDelegateDeclaration(SyntaxNodeAnalysisContext context) { var delegateDeclaration = (DelegateDeclarationSyntax)context.Node; - - if (delegateDeclaration.ParameterList == null || - delegateDeclaration.ParameterList.IsMissing || - !delegateDeclaration.ParameterList.Parameters.Any()) - { - return; - } - - var lastParameter = delegateDeclaration.ParameterList - .Parameters - .Last(); - - if (!delegateDeclaration.ParameterList.CloseParenToken.IsMissing) - { - CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( - context, - lastParameter, - delegateDeclaration.ParameterList.CloseParenToken); - } + CheckParameterList(context, delegateDeclaration.ParameterList); } private static void HandleElementAccessExpression(SyntaxNodeAnalysisContext context) { var elementAccess = (ElementAccessExpressionSyntax)context.Node; + var argumentList = elementAccess.ArgumentList; - if (elementAccess.ArgumentList == null || - elementAccess.ArgumentList.IsMissing || - !elementAccess.ArgumentList.Arguments.Any()) + if (argumentList == null || argumentList.IsMissing || !argumentList.Arguments.Any()) { return; } - var lastArgument = elementAccess.ArgumentList - .Arguments - .Last(); + var lastParameter = argumentList.Arguments.Last(); - if (!elementAccess.ArgumentList.CloseBracketToken.IsMissing && !lastArgument.IsMissing) - { - CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( - context, - lastArgument, - elementAccess.ArgumentList.CloseBracketToken); - } + CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( + context, + lastParameter, + argumentList.CloseBracketToken); } private static void HandleInvocationExpression(SyntaxNodeAnalysisContext context) { var invocationExpression = (InvocationExpressionSyntax)context.Node; - - if (invocationExpression.ArgumentList == null || - invocationExpression.ArgumentList.IsMissing || - !invocationExpression.ArgumentList.Arguments.Any()) - { - return; - } - - var lastArgument = invocationExpression.ArgumentList - .Arguments - .Last(); - - if (!invocationExpression.ArgumentList.CloseParenToken.IsMissing && - !lastArgument.IsMissing) - { - CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( - context, - lastArgument, - invocationExpression.ArgumentList.CloseParenToken); - } + CheckArgumentList(context, invocationExpression.ArgumentList); } private static void HandleObjectCreationExpression(SyntaxNodeAnalysisContext context) { var objectCreation = (ObjectCreationExpressionSyntax)context.Node; - - if (objectCreation.ArgumentList == null || - objectCreation.ArgumentList.IsMissing || - !objectCreation.ArgumentList.Arguments.Any()) - { - return; - } - - var lastArgument = objectCreation.ArgumentList - .Arguments - .Last(); - - if (!objectCreation.ArgumentList.CloseParenToken.IsMissing && - !lastArgument.IsMissing) - { - CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( - context, - lastArgument, - objectCreation.ArgumentList.CloseParenToken); - } + CheckArgumentList(context, objectCreation.ArgumentList); } private static void HandleIndexerDeclaration(SyntaxNodeAnalysisContext context) { var indexerDeclaration = (IndexerDeclarationSyntax)context.Node; + var parameterList = indexerDeclaration.ParameterList; - if (indexerDeclaration.ParameterList == null || - indexerDeclaration.ParameterList.IsMissing || - !indexerDeclaration.ParameterList.Parameters.Any()) + if (parameterList == null || parameterList.IsMissing || !parameterList.Parameters.Any()) { return; } - var lastParameter = indexerDeclaration.ParameterList - .Parameters - .Last(); + var lastParameter = parameterList.Parameters.Last(); - if (!indexerDeclaration.ParameterList.CloseBracketToken.IsMissing) - { - CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame(context, lastParameter, indexerDeclaration.ParameterList.CloseBracketToken); - } + CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( + context, + lastParameter, + parameterList.CloseBracketToken); } private static void HandleBaseMethodDeclaration(SyntaxNodeAnalysisContext context) { var baseMethodDeclarationSyntax = (BaseMethodDeclarationSyntax)context.Node; + CheckParameterList(context, baseMethodDeclarationSyntax.ParameterList); + } + + private static void HandleLocalFunctionStatement(SyntaxNodeAnalysisContext context) + { + var localFunctionStatementSyntax = (LocalFunctionStatementSyntaxWrapper)context.Node; + CheckParameterList(context, localFunctionStatementSyntax.ParameterList); + } - if (baseMethodDeclarationSyntax.ParameterList == null || - baseMethodDeclarationSyntax.ParameterList.IsMissing || - !baseMethodDeclarationSyntax.ParameterList.Parameters.Any()) + private static void HandleTypeDeclaration(SyntaxNodeAnalysisContext context) + { + var typeDeclarationSyntax = (TypeDeclarationSyntax)context.Node; + CheckParameterList(context, typeDeclarationSyntax.ParameterList()); + } + + private static void HandlePrimaryConstructorBaseType(SyntaxNodeAnalysisContext context) + { + var typeDeclarationSyntax = (PrimaryConstructorBaseTypeSyntaxWrapper)context.Node; + CheckArgumentList(context, typeDeclarationSyntax.ArgumentList); + } + + private static void CheckParameterList(SyntaxNodeAnalysisContext context, ParameterListSyntax parameterList) + { + if (parameterList == null || parameterList.IsMissing || !parameterList.Parameters.Any()) { return; } - var lastParameter = baseMethodDeclarationSyntax.ParameterList - .Parameters - .Last(); + var lastParameter = parameterList.Parameters.Last(); - if (!baseMethodDeclarationSyntax.ParameterList.CloseParenToken.IsMissing) - { - CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame(context, lastParameter, baseMethodDeclarationSyntax.ParameterList.CloseParenToken); - } + CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( + context, + lastParameter, + parameterList.CloseParenToken); } - private static void HandleLocalFunctionStatement(SyntaxNodeAnalysisContext context) + private static void CheckArgumentList(SyntaxNodeAnalysisContext context, ArgumentListSyntax argumentList) { - var localFunctionStatementSyntax = (LocalFunctionStatementSyntaxWrapper)context.Node; - - if (localFunctionStatementSyntax.ParameterList == null || - localFunctionStatementSyntax.ParameterList.IsMissing || - !localFunctionStatementSyntax.ParameterList.Parameters.Any()) + if (argumentList == null || argumentList.IsMissing || !argumentList.Arguments.Any()) { return; } - var lastParameter = localFunctionStatementSyntax.ParameterList - .Parameters - .Last(); + var lastParameter = argumentList.Arguments.Last(); - if (!localFunctionStatementSyntax.ParameterList.CloseParenToken.IsMissing) - { - CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame(context, lastParameter, localFunctionStatementSyntax.ParameterList.CloseParenToken); - } + CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( + context, + lastParameter, + argumentList.CloseParenToken); } private static void CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( @@ -362,6 +267,11 @@ private static void CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheS CSharpSyntaxNode parameterOrArgument, SyntaxToken closeToken) { + if (parameterOrArgument.IsMissing || closeToken.IsMissing) + { + return; + } + var lastParameterLine = parameterOrArgument.GetLineSpan(); var closeParenLine = closeToken.GetLineSpan(); if (lastParameterLine.IsValid &&