From a6d0cb29f31712237e75d5286e1dad2d2871a59b Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 8 Dec 2021 11:13:39 -0800 Subject: [PATCH 1/2] Improve lightup support for BaseNamespaceDeclarationSyntax --- .../SyntaxLightupGenerator.cs | 59 +++++++++++++++++++ .../Lightup/SyntaxWrapperHelperTests.cs | 6 ++ .../SyntaxWrapperHelper.g.cs | 4 +- .../BaseNamespaceDeclarationSyntaxWrapper.cs | 17 ++++++ .../Lightup/SyntaxKindEx.cs | 1 + 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers/Lightup/BaseNamespaceDeclarationSyntaxWrapper.cs diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeGeneration/SyntaxLightupGenerator.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeGeneration/SyntaxLightupGenerator.cs index abd06fbd5..0413fbe80 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeGeneration/SyntaxLightupGenerator.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeGeneration/SyntaxLightupGenerator.cs @@ -986,6 +986,65 @@ private void GenerateSyntaxWrapperHelper(in SourceProductionContext context, Imm continue; } + if (node.Name == nameof(BaseNamespaceDeclarationSyntax)) + { + // Prior to C# 10, NamespaceDeclarationSyntax was the base type for all namespace declarations. + // If the BaseNamespaceDeclarationSyntax type isn't found at runtime, we fall back + // to using this type instead. + // + // var baseNamespaceDeclarationSyntaxType = csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.WrappedTypeName) + // ?? csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.WrappedTypeName); + LocalDeclarationStatementSyntax localStatement = + SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.VariableDeclaration( + type: SyntaxFactory.IdentifierName("var"), + variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( + identifier: SyntaxFactory.Identifier("baseNamespaceDeclarationSyntaxType"), + argumentList: null, + initializer: SyntaxFactory.EqualsValueClause( + SyntaxFactory.BinaryExpression( + SyntaxKind.CoalesceExpression, + left: SyntaxFactory.InvocationExpression( + expression: SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + expression: SyntaxFactory.IdentifierName("csharpCodeAnalysisAssembly"), + name: SyntaxFactory.IdentifierName("GetType")), + argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + expression: SyntaxFactory.IdentifierName(node.WrapperName), + name: SyntaxFactory.IdentifierName("WrappedTypeName")))))), + right: SyntaxFactory.InvocationExpression( + expression: SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + expression: SyntaxFactory.IdentifierName("csharpCodeAnalysisAssembly"), + name: SyntaxFactory.IdentifierName("GetType")), + argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + expression: SyntaxFactory.IdentifierName(node.WrapperName), + name: SyntaxFactory.IdentifierName("FallbackWrappedTypeName")))))))))))); + + // This is the first line of the statements that initialize 'builder', so start it with a blank line + staticCtorStatements = staticCtorStatements.Add(localStatement.WithLeadingBlankLine()); + + // builder.Add(typeof(BaseNamespaceDeclarationSyntaxWrapper), baseNamespaceDeclarationSyntaxType); + staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement( + SyntaxFactory.InvocationExpression( + expression: SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + expression: SyntaxFactory.IdentifierName("builder"), + name: SyntaxFactory.IdentifierName("Add")), + argumentList: SyntaxFactory.ArgumentList( + SyntaxFactory.SeparatedList( + new[] + { + SyntaxFactory.Argument(SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName(node.WrapperName))), + SyntaxFactory.Argument(SyntaxFactory.IdentifierName("baseNamespaceDeclarationSyntaxType")), + }))))); + + continue; + } + // builder.Add(typeof(ConstantPatternSyntaxWrapper), csharpCodeAnalysisAssembly.GetType(ConstantPatternSyntaxWrapper.WrappedTypeName)); staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Lightup/SyntaxWrapperHelperTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Lightup/SyntaxWrapperHelperTests.cs index 20311d045..5cd72dacc 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Lightup/SyntaxWrapperHelperTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Lightup/SyntaxWrapperHelperTests.cs @@ -50,6 +50,12 @@ public void VerifyThatWrapperClassIsPresent(Type wrapperType) Assert.False(LightupHelpers.SupportsCSharp9); Assert.Same(typeof(ObjectCreationExpressionSyntax), SyntaxWrapperHelper.GetWrappedType(wrapperType)); } + else if (wrapperType == typeof(BaseNamespaceDeclarationSyntaxWrapper)) + { + // Special case for C# 6-9 analysis compatibility + Assert.False(LightupHelpers.SupportsCSharp10); + Assert.Same(typeof(NamespaceDeclarationSyntax), SyntaxWrapperHelper.GetWrappedType(wrapperType)); + } else { Assert.Null(SyntaxWrapperHelper.GetWrappedType(wrapperType)); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/.generated/StyleCop.Analyzers.CodeGeneration/StyleCop.Analyzers.CodeGeneration.SyntaxLightupGenerator/SyntaxWrapperHelper.g.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/.generated/StyleCop.Analyzers.CodeGeneration/StyleCop.Analyzers.CodeGeneration.SyntaxLightupGenerator/SyntaxWrapperHelper.g.cs index 6d88cc6b8..1cbe26b7f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/.generated/StyleCop.Analyzers.CodeGeneration/StyleCop.Analyzers.CodeGeneration.SyntaxLightupGenerator/SyntaxWrapperHelper.g.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/.generated/StyleCop.Analyzers.CodeGeneration/StyleCop.Analyzers.CodeGeneration.SyntaxLightupGenerator/SyntaxWrapperHelper.g.cs @@ -18,7 +18,9 @@ static SyntaxWrapperHelper() var csharpCodeAnalysisAssembly = typeof(CSharpSyntaxNode).GetTypeInfo().Assembly; var builder = ImmutableDictionary.CreateBuilder(); builder.Add(typeof(BaseExpressionColonSyntaxWrapper), csharpCodeAnalysisAssembly.GetType(BaseExpressionColonSyntaxWrapper.WrappedTypeName)); - builder.Add(typeof(BaseNamespaceDeclarationSyntaxWrapper), csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.WrappedTypeName)); + + var baseNamespaceDeclarationSyntaxType = csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.WrappedTypeName) ?? csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.FallbackWrappedTypeName); + builder.Add(typeof(BaseNamespaceDeclarationSyntaxWrapper), baseNamespaceDeclarationSyntaxType); var objectCreationExpressionSyntaxType = csharpCodeAnalysisAssembly.GetType(BaseObjectCreationExpressionSyntaxWrapper.WrappedTypeName) ?? csharpCodeAnalysisAssembly.GetType(BaseObjectCreationExpressionSyntaxWrapper.FallbackWrappedTypeName); builder.Add(typeof(BaseObjectCreationExpressionSyntaxWrapper), objectCreationExpressionSyntaxType); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/BaseNamespaceDeclarationSyntaxWrapper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/BaseNamespaceDeclarationSyntaxWrapper.cs new file mode 100644 index 000000000..6b27c6163 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/BaseNamespaceDeclarationSyntaxWrapper.cs @@ -0,0 +1,17 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Lightup +{ + using Microsoft.CodeAnalysis.CSharp.Syntax; + + internal partial struct BaseNamespaceDeclarationSyntaxWrapper : ISyntaxWrapper + { + internal const string FallbackWrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax"; + + public static implicit operator BaseNamespaceDeclarationSyntaxWrapper(NamespaceDeclarationSyntax node) + { + return new BaseNamespaceDeclarationSyntaxWrapper(node); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs index 11eb92a3c..76aa8be7f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs @@ -25,6 +25,7 @@ internal static class SyntaxKindEx public const SyntaxKind IndexExpression = (SyntaxKind)8741; public const SyntaxKind DefaultLiteralExpression = (SyntaxKind)8755; public const SyntaxKind LocalFunctionStatement = (SyntaxKind)8830; + public const SyntaxKind FileScopedNamespaceDeclaration = (SyntaxKind)8845; public const SyntaxKind TupleType = (SyntaxKind)8924; public const SyntaxKind TupleElement = (SyntaxKind)8925; public const SyntaxKind TupleExpression = (SyntaxKind)8926; From 31cd1a5429169eb3a3e78d49d5a645a3089ae3e2 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 8 Dec 2021 11:14:14 -0800 Subject: [PATCH 2/2] Update SA1135 to support file-scoped namespaces Fixes #3415 --- .../SA1135CSharp10UnitTests.cs | 44 +++++++++++++++++++ .../SA1135UsingDirectivesMustBeQualified.cs | 5 ++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1135CSharp10UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1135CSharp10UnitTests.cs index fcbb238de..0c00bbd48 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1135CSharp10UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1135CSharp10UnitTests.cs @@ -3,9 +3,53 @@ namespace StyleCop.Analyzers.Test.CSharp10.ReadabilityRules { + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp9.ReadabilityRules; + using Xunit; + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1135UsingDirectivesMustBeQualified, + StyleCop.Analyzers.ReadabilityRules.SA1135CodeFixProvider>; public class SA1135CSharp10UnitTests : SA1135CSharp9UnitTests { + [Fact] + [WorkItem(3415, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3415")] + public async Task TestFileScopedNamespaceAsync() + { + var testCode = @" +namespace TestNamespace +{ + using KeyValue = System.Collections.Generic.KeyValuePair; +} +"; + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await new CSharpTest + { + TestState = + { + Sources = + { + @"namespace A.B.C { }", + @"namespace A.B.D; + +[|using C;|] +", + }, + }, + FixedState = + { + Sources = + { + @"namespace A.B.C { }", + @"namespace A.B.D; + +using A.B.C; +", + }, + }, + }.RunAsync(CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs index 0c1f34939..9307a3430 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs @@ -67,7 +67,8 @@ private static void HandleUsingDeclaration(SyntaxNodeAnalysisContext context) private static void CheckUsingDeclaration(SyntaxNodeAnalysisContext context, UsingDirectiveSyntax usingDirective) { - if (!usingDirective.Parent.IsKind(SyntaxKind.NamespaceDeclaration)) + if (!usingDirective.Parent.IsKind(SyntaxKind.NamespaceDeclaration) + && !usingDirective.Parent.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration)) { // Usings outside of a namespace are always qualified. return; @@ -104,7 +105,7 @@ private static void CheckUsingDeclaration(SyntaxNodeAnalysisContext context, Usi break; case SymbolKind.NamedType: - var containingNamespace = ((NamespaceDeclarationSyntax)usingDirective.Parent).Name.ToString(); + var containingNamespace = ((BaseNamespaceDeclarationSyntaxWrapper)usingDirective.Parent).Name.ToString(); if (containingNamespace != symbol.ContainingNamespace.ToString()) { context.ReportDiagnostic(Diagnostic.Create(DescriptorType, usingDirective.GetLocation(), symbolString));