From f05e5416e80f03ddd6c3da2c2619b1541ea7b3cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Tue, 19 Mar 2024 21:37:33 +0100 Subject: [PATCH 1/4] Handle comments from PR #3800 and #3801 --- .../DocumentationRules/SA1612UnitTests.cs | 6 ++--- .../Helpers/CommonMemberData.cs | 23 +++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) 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" }; + } } } } From b5992efacd6c5fad53064877cd003ed602d48b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Tue, 19 Mar 2024 21:37:54 +0100 Subject: [PATCH 2/4] Simplify code in SA1111ClosingParenthesisMustBeOnLineOfLastParameter --- ...gParenthesisMustBeOnLineOfLastParameter.cs | 224 +++++------------- 1 file changed, 59 insertions(+), 165 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs index a965afcba..22d3d9aae 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs @@ -111,250 +111,139 @@ 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 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 +251,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 && From 92e8a2ca3fb11e823603332c945679a6d637e792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Tue, 19 Mar 2024 21:40:38 +0100 Subject: [PATCH 3/4] Update SA1111 to also check the parameter list in primary constructors #3785 --- .../SA1111CSharp11UnitTests.cs | 11 ++++ .../SA1111CSharp9UnitTests.cs | 51 +++++++++++++++++++ ...gParenthesisMustBeOnLineOfLastParameter.cs | 8 +++ 3 files changed, 70 insertions(+) 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..c90302f9f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1111CSharp9UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1111CSharp9UnitTests.cs @@ -3,9 +3,60 @@ 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); + } + + protected virtual DiagnosticResult[] GetExpectedResultTestPrimaryConstructorWithParameter() + { + return new[] + { + // Diagnostic issued twice because of https://github.com/dotnet/roslyn/issues/53136 + Diagnostic().WithLocation(0), + Diagnostic().WithLocation(0), + }; + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs index 22d3d9aae..df560cf69 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs @@ -60,6 +60,7 @@ internal class SA1111ClosingParenthesisMustBeOnLineOfLastParameter : DiagnosticA SyntaxKind.OperatorDeclaration, SyntaxKind.ConversionOperatorDeclaration); + private static readonly Action TypeDeclarationAction = HandleTypeDeclaration; private static readonly Action BaseMethodDeclarationAction = HandleBaseMethodDeclaration; private static readonly Action LocalFunctionStatementAction = HandleLocalFunctionStatement; private static readonly Action InvocationExpressionAction = HandleInvocationExpression; @@ -82,6 +83,7 @@ public override void Initialize(AnalysisContext context) context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(TypeDeclarationAction, SyntaxKinds.TypeDeclaration); context.RegisterSyntaxNodeAction(BaseMethodDeclarationAction, HandledMethodSyntaxKinds); context.RegisterSyntaxNodeAction(LocalFunctionStatementAction, SyntaxKindEx.LocalFunctionStatement); context.RegisterSyntaxNodeAction(InvocationExpressionAction, SyntaxKind.InvocationExpression); @@ -216,6 +218,12 @@ private static void HandleLocalFunctionStatement(SyntaxNodeAnalysisContext conte CheckParameterList(context, localFunctionStatementSyntax.ParameterList); } + private static void HandleTypeDeclaration(SyntaxNodeAnalysisContext context) + { + var typeDeclarationSyntax = (TypeDeclarationSyntax)context.Node; + CheckParameterList(context, typeDeclarationSyntax.ParameterList()); + } + private static void CheckParameterList(SyntaxNodeAnalysisContext context, ParameterListSyntax parameterList) { if (parameterList == null || parameterList.IsMissing || !parameterList.Parameters.Any()) From 5f003810ca000dce32be5c342d34dc017d63a985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Wed, 20 Mar 2024 22:16:56 +0100 Subject: [PATCH 4/4] Update SA1111 to also check the primary constructor argument list in a base list #3785 --- .../SA1111CSharp9UnitTests.cs | 56 +++++++++++++++++++ ...gParenthesisMustBeOnLineOfLastParameter.cs | 8 +++ 2 files changed, 64 insertions(+) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1111CSharp9UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1111CSharp9UnitTests.cs index c90302f9f..5e0b146ff 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1111CSharp9UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1111CSharp9UnitTests.cs @@ -49,6 +49,52 @@ public async Task TestPrimaryConstructorWithoutParameterAsync(string typeKeyword 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[] @@ -58,5 +104,15 @@ protected virtual DiagnosticResult[] GetExpectedResultTestPrimaryConstructorWith 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/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs index df560cf69..d348346ec 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs @@ -61,6 +61,7 @@ internal class SA1111ClosingParenthesisMustBeOnLineOfLastParameter : DiagnosticA 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; @@ -84,6 +85,7 @@ public override void Initialize(AnalysisContext context) 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); @@ -224,6 +226,12 @@ private static void HandleTypeDeclaration(SyntaxNodeAnalysisContext context) 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())