Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update SA1135 to support file-scoped namespaces #3419

Merged
merged 2 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object?>;
}
";
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ static SyntaxWrapperHelper()
var csharpCodeAnalysisAssembly = typeof(CSharpSyntaxNode).GetTypeInfo().Assembly;
var builder = ImmutableDictionary.CreateBuilder<Type, Type>();
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<MemberDeclarationSyntax>
{
internal const string FallbackWrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax";

public static implicit operator BaseNamespaceDeclarationSyntaxWrapper(NamespaceDeclarationSyntax node)
{
return new BaseNamespaceDeclarationSyntaxWrapper(node);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down