Skip to content

Commit 0b40032

Browse files
authored
Merge pull request #3419 from sharwell/file-scoped-namespace
Update SA1135 to support file-scoped namespaces
2 parents 08588cd + 31cd1a5 commit 0b40032

File tree

7 files changed

+133
-3
lines changed

7 files changed

+133
-3
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.CodeGeneration/SyntaxLightupGenerator.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,65 @@ private void GenerateSyntaxWrapperHelper(in SourceProductionContext context, Imm
986986
continue;
987987
}
988988

989+
if (node.Name == nameof(BaseNamespaceDeclarationSyntax))
990+
{
991+
// Prior to C# 10, NamespaceDeclarationSyntax was the base type for all namespace declarations.
992+
// If the BaseNamespaceDeclarationSyntax type isn't found at runtime, we fall back
993+
// to using this type instead.
994+
//
995+
// var baseNamespaceDeclarationSyntaxType = csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.WrappedTypeName)
996+
// ?? csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.WrappedTypeName);
997+
LocalDeclarationStatementSyntax localStatement =
998+
SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.VariableDeclaration(
999+
type: SyntaxFactory.IdentifierName("var"),
1000+
variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(
1001+
identifier: SyntaxFactory.Identifier("baseNamespaceDeclarationSyntaxType"),
1002+
argumentList: null,
1003+
initializer: SyntaxFactory.EqualsValueClause(
1004+
SyntaxFactory.BinaryExpression(
1005+
SyntaxKind.CoalesceExpression,
1006+
left: SyntaxFactory.InvocationExpression(
1007+
expression: SyntaxFactory.MemberAccessExpression(
1008+
SyntaxKind.SimpleMemberAccessExpression,
1009+
expression: SyntaxFactory.IdentifierName("csharpCodeAnalysisAssembly"),
1010+
name: SyntaxFactory.IdentifierName("GetType")),
1011+
argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(
1012+
SyntaxFactory.MemberAccessExpression(
1013+
SyntaxKind.SimpleMemberAccessExpression,
1014+
expression: SyntaxFactory.IdentifierName(node.WrapperName),
1015+
name: SyntaxFactory.IdentifierName("WrappedTypeName")))))),
1016+
right: SyntaxFactory.InvocationExpression(
1017+
expression: SyntaxFactory.MemberAccessExpression(
1018+
SyntaxKind.SimpleMemberAccessExpression,
1019+
expression: SyntaxFactory.IdentifierName("csharpCodeAnalysisAssembly"),
1020+
name: SyntaxFactory.IdentifierName("GetType")),
1021+
argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(
1022+
SyntaxFactory.MemberAccessExpression(
1023+
SyntaxKind.SimpleMemberAccessExpression,
1024+
expression: SyntaxFactory.IdentifierName(node.WrapperName),
1025+
name: SyntaxFactory.IdentifierName("FallbackWrappedTypeName"))))))))))));
1026+
1027+
// This is the first line of the statements that initialize 'builder', so start it with a blank line
1028+
staticCtorStatements = staticCtorStatements.Add(localStatement.WithLeadingBlankLine());
1029+
1030+
// builder.Add(typeof(BaseNamespaceDeclarationSyntaxWrapper), baseNamespaceDeclarationSyntaxType);
1031+
staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement(
1032+
SyntaxFactory.InvocationExpression(
1033+
expression: SyntaxFactory.MemberAccessExpression(
1034+
SyntaxKind.SimpleMemberAccessExpression,
1035+
expression: SyntaxFactory.IdentifierName("builder"),
1036+
name: SyntaxFactory.IdentifierName("Add")),
1037+
argumentList: SyntaxFactory.ArgumentList(
1038+
SyntaxFactory.SeparatedList(
1039+
new[]
1040+
{
1041+
SyntaxFactory.Argument(SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName(node.WrapperName))),
1042+
SyntaxFactory.Argument(SyntaxFactory.IdentifierName("baseNamespaceDeclarationSyntaxType")),
1043+
})))));
1044+
1045+
continue;
1046+
}
1047+
9891048
// builder.Add(typeof(ConstantPatternSyntaxWrapper), csharpCodeAnalysisAssembly.GetType(ConstantPatternSyntaxWrapper.WrappedTypeName));
9901049
staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement(
9911050
SyntaxFactory.InvocationExpression(

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/ReadabilityRules/SA1135CSharp10UnitTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,53 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp10.ReadabilityRules
55
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.Testing;
69
using StyleCop.Analyzers.Test.CSharp9.ReadabilityRules;
10+
using Xunit;
11+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
12+
StyleCop.Analyzers.ReadabilityRules.SA1135UsingDirectivesMustBeQualified,
13+
StyleCop.Analyzers.ReadabilityRules.SA1135CodeFixProvider>;
714

815
public class SA1135CSharp10UnitTests : SA1135CSharp9UnitTests
916
{
17+
[Fact]
18+
[WorkItem(3415, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3415")]
19+
public async Task TestFileScopedNamespaceAsync()
20+
{
21+
var testCode = @"
22+
namespace TestNamespace
23+
{
24+
using KeyValue = System.Collections.Generic.KeyValuePair<string, object?>;
25+
}
26+
";
27+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
28+
await new CSharpTest
29+
{
30+
TestState =
31+
{
32+
Sources =
33+
{
34+
@"namespace A.B.C { }",
35+
@"namespace A.B.D;
36+
37+
[|using C;|]
38+
",
39+
},
40+
},
41+
FixedState =
42+
{
43+
Sources =
44+
{
45+
@"namespace A.B.C { }",
46+
@"namespace A.B.D;
47+
48+
using A.B.C;
49+
",
50+
},
51+
},
52+
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
53+
}
1054
}
1155
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/Lightup/SyntaxWrapperHelperTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ public void VerifyThatWrapperClassIsPresent(Type wrapperType)
5050
Assert.False(LightupHelpers.SupportsCSharp9);
5151
Assert.Same(typeof(ObjectCreationExpressionSyntax), SyntaxWrapperHelper.GetWrappedType(wrapperType));
5252
}
53+
else if (wrapperType == typeof(BaseNamespaceDeclarationSyntaxWrapper))
54+
{
55+
// Special case for C# 6-9 analysis compatibility
56+
Assert.False(LightupHelpers.SupportsCSharp10);
57+
Assert.Same(typeof(NamespaceDeclarationSyntax), SyntaxWrapperHelper.GetWrappedType(wrapperType));
58+
}
5359
else
5460
{
5561
Assert.Null(SyntaxWrapperHelper.GetWrappedType(wrapperType));

StyleCop.Analyzers/StyleCop.Analyzers/Lightup/.generated/StyleCop.Analyzers.CodeGeneration/StyleCop.Analyzers.CodeGeneration.SyntaxLightupGenerator/SyntaxWrapperHelper.g.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ static SyntaxWrapperHelper()
1818
var csharpCodeAnalysisAssembly = typeof(CSharpSyntaxNode).GetTypeInfo().Assembly;
1919
var builder = ImmutableDictionary.CreateBuilder<Type, Type>();
2020
builder.Add(typeof(BaseExpressionColonSyntaxWrapper), csharpCodeAnalysisAssembly.GetType(BaseExpressionColonSyntaxWrapper.WrappedTypeName));
21-
builder.Add(typeof(BaseNamespaceDeclarationSyntaxWrapper), csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.WrappedTypeName));
21+
22+
var baseNamespaceDeclarationSyntaxType = csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.WrappedTypeName) ?? csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.FallbackWrappedTypeName);
23+
builder.Add(typeof(BaseNamespaceDeclarationSyntaxWrapper), baseNamespaceDeclarationSyntaxType);
2224

2325
var objectCreationExpressionSyntaxType = csharpCodeAnalysisAssembly.GetType(BaseObjectCreationExpressionSyntaxWrapper.WrappedTypeName) ?? csharpCodeAnalysisAssembly.GetType(BaseObjectCreationExpressionSyntaxWrapper.FallbackWrappedTypeName);
2426
builder.Add(typeof(BaseObjectCreationExpressionSyntaxWrapper), objectCreationExpressionSyntaxType);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Lightup
5+
{
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
8+
internal partial struct BaseNamespaceDeclarationSyntaxWrapper : ISyntaxWrapper<MemberDeclarationSyntax>
9+
{
10+
internal const string FallbackWrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax";
11+
12+
public static implicit operator BaseNamespaceDeclarationSyntaxWrapper(NamespaceDeclarationSyntax node)
13+
{
14+
return new BaseNamespaceDeclarationSyntaxWrapper(node);
15+
}
16+
}
17+
}

StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ internal static class SyntaxKindEx
2525
public const SyntaxKind IndexExpression = (SyntaxKind)8741;
2626
public const SyntaxKind DefaultLiteralExpression = (SyntaxKind)8755;
2727
public const SyntaxKind LocalFunctionStatement = (SyntaxKind)8830;
28+
public const SyntaxKind FileScopedNamespaceDeclaration = (SyntaxKind)8845;
2829
public const SyntaxKind TupleType = (SyntaxKind)8924;
2930
public const SyntaxKind TupleElement = (SyntaxKind)8925;
3031
public const SyntaxKind TupleExpression = (SyntaxKind)8926;

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1135UsingDirectivesMustBeQualified.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ private static void HandleUsingDeclaration(SyntaxNodeAnalysisContext context)
6767

6868
private static void CheckUsingDeclaration(SyntaxNodeAnalysisContext context, UsingDirectiveSyntax usingDirective)
6969
{
70-
if (!usingDirective.Parent.IsKind(SyntaxKind.NamespaceDeclaration))
70+
if (!usingDirective.Parent.IsKind(SyntaxKind.NamespaceDeclaration)
71+
&& !usingDirective.Parent.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration))
7172
{
7273
// Usings outside of a namespace are always qualified.
7374
return;
@@ -104,7 +105,7 @@ private static void CheckUsingDeclaration(SyntaxNodeAnalysisContext context, Usi
104105
break;
105106

106107
case SymbolKind.NamedType:
107-
var containingNamespace = ((NamespaceDeclarationSyntax)usingDirective.Parent).Name.ToString();
108+
var containingNamespace = ((BaseNamespaceDeclarationSyntaxWrapper)usingDirective.Parent).Name.ToString();
108109
if (containingNamespace != symbol.ContainingNamespace.ToString())
109110
{
110111
context.ReportDiagnostic(Diagnostic.Create(DescriptorType, usingDirective.GetLocation(), symbolString));

0 commit comments

Comments
 (0)