Skip to content

Commit 9f42261

Browse files
committed
Added additional unit tests and improved the analyzer
1 parent ec9e263 commit 9f42261

3 files changed

Lines changed: 111 additions & 19 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1135UnitTests.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,63 @@ namespace System.Threading
148148
await this.VerifyCSharpFixAllFixAsync(testCode, fixedCode, numberOfIterations: 1, cancellationToken: CancellationToken.None).ConfigureAwait(false);
149149
}
150150

151+
[Fact]
152+
public async Task TestNoWarningsForAliasesAsync()
153+
{
154+
var testCode = @"
155+
using Tasks = System.Threading.Tasks;
156+
157+
namespace Namespace
158+
{
159+
using Task = Tasks.Task;
160+
}
161+
";
162+
163+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
164+
}
165+
166+
[Fact]
167+
public async Task TestAliasToGenericTypeAsync()
168+
{
169+
var testCode = @"
170+
namespace System.Collections
171+
{
172+
using Dictionary = Generic.Dictionary<int, string>;
173+
}
174+
";
175+
176+
var fixedCode = @"
177+
namespace System.Collections
178+
{
179+
using Dictionary = System.Collections.Generic.Dictionary<int, string>;
180+
}
181+
";
182+
183+
DiagnosticResult[] expected =
184+
{
185+
this.CSharpDiagnostic(SA1135UsingDirectivesMustBeQualified.DescriptorType).WithLocation(4, 5).WithArguments("System.Collections.Generic.Dictionary<int, string>"),
186+
};
187+
188+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
189+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
190+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
191+
}
192+
193+
[Fact]
194+
public async Task TestAliasToTypesInSameNamespaceAsync()
195+
{
196+
var testCode = @"
197+
namespace Namespace
198+
{
199+
using Class2 = Class;
200+
201+
class Class { }
202+
}
203+
";
204+
205+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
206+
}
207+
151208
protected override CodeFixProvider GetCSharpCodeFixProvider()
152209
{
153210
return new SA1135CodeFixProvider();

StyleCop.Analyzers/StyleCop.Analyzers/Helpers/UsingDirectiveSyntaxHelpers.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.Helpers
55
{
66
using System;
77
using System.Linq;
8+
using System.Threading;
89
using Microsoft.CodeAnalysis;
910
using Microsoft.CodeAnalysis.CSharp;
1011
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -84,6 +85,19 @@ internal static UsingGroup GetUsingGroupType(this UsingDirectiveSyntax usingDire
8485
return UsingGroup.Regular;
8586
}
8687

88+
/// <summary>
89+
/// Checks if the Name part of the given using directive starts with an alias.
90+
/// </summary>
91+
/// <param name="usingDirective">The <see cref="UsingDirectiveSyntax"/> that will be used.</param>
92+
/// <param name="semanticModel">The <see cref="SemanticModel"/> that will be used.</param>
93+
/// <param name="cancellationToken">The cancellation token that can be used to interrupt the operation.</param>
94+
/// <returns>True if the name part of the using directive starts with an alias.</returns>
95+
internal static bool StartsWithAlias(this UsingDirectiveSyntax usingDirective, SemanticModel semanticModel, CancellationToken cancellationToken)
96+
{
97+
var firstPart = usingDirective.Name.DescendantNodes().FirstOrDefault() ?? usingDirective.Name;
98+
return semanticModel.GetAliasInfo(firstPart, cancellationToken) != null;
99+
}
100+
87101
private static bool ExcludeGlobalKeyword(IdentifierNameSyntax token) => !token.Identifier.IsKind(SyntaxKind.GlobalKeyword);
88102

89103
private static SyntaxToken? GetFirstIdentifierInUsingDirective(UsingDirectiveSyntax usingDirective)

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

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ namespace StyleCop.Analyzers.ReadabilityRules
1010
using Microsoft.CodeAnalysis.CSharp.Syntax;
1111
using Microsoft.CodeAnalysis.Diagnostics;
1212

13+
using StyleCop.Analyzers.Helpers;
14+
1315
/// <summary>
1416
/// A using directive is not qualified.
1517
/// </summary>
@@ -63,30 +65,49 @@ private static void HandleUsingDeclaration(SyntaxNodeAnalysisContext context)
6365

6466
private static void CheckUsingDeclaration(SyntaxNodeAnalysisContext context, UsingDirectiveSyntax usingDirective)
6567
{
66-
// Usings outside of a namespace are always qualified.
67-
if (usingDirective.Parent is NamespaceDeclarationSyntax && usingDirective.StaticKeyword.IsKind(SyntaxKind.None))
68+
if (!usingDirective.Parent.IsKind(SyntaxKind.NamespaceDeclaration))
69+
{
70+
// Usings outside of a namespace are always qualified.
71+
return;
72+
}
73+
74+
if (!usingDirective.StaticKeyword.IsKind(SyntaxKind.None))
75+
{
76+
// using static types is not considered.
77+
return;
78+
}
79+
80+
string usingString = usingDirective.Name.ToString();
81+
if (usingString.IndexOf("::", StringComparison.Ordinal) >= 0)
6882
{
69-
string usingString = usingDirective.Name.ToString();
83+
// global qualified namespaces are OK.
84+
return;
85+
}
86+
87+
var symbol = context.SemanticModel.GetSymbolInfo(usingDirective.Name, context.CancellationToken).Symbol;
88+
if (symbol == null)
89+
{
90+
// if there is no symbol, do not proceed.
91+
return;
92+
}
7093

71-
// Check for global qualified namespaces.
72-
if (usingString.IndexOf("::", StringComparison.Ordinal) < 0)
94+
string symbolString = symbol.ToString();
95+
if ((symbolString != usingString) && !usingDirective.StartsWithAlias(context.SemanticModel, context.CancellationToken))
96+
{
97+
switch (symbol.Kind)
7398
{
74-
SymbolInfo symbolInfo = context.SemanticModel.GetSymbolInfo(usingDirective.Name, context.CancellationToken);
75-
if (symbolInfo.Symbol != null && (symbolInfo.Symbol.Kind == SymbolKind.Namespace || symbolInfo.Symbol.Kind == SymbolKind.NamedType))
99+
case SymbolKind.Namespace:
100+
context.ReportDiagnostic(Diagnostic.Create(DescriptorNamespace, usingDirective.GetLocation(), symbolString));
101+
break;
102+
103+
case SymbolKind.NamedType:
104+
var containingNamespace = ((NamespaceDeclarationSyntax)usingDirective.Parent).Name.ToString();
105+
if (containingNamespace != symbol.ContainingNamespace.ToString())
76106
{
77-
string symbolString = symbolInfo.Symbol.ToString();
78-
if (symbolString != usingString)
79-
{
80-
if (symbolInfo.Symbol.Kind == SymbolKind.NamedType)
81-
{
82-
context.ReportDiagnostic(Diagnostic.Create(DescriptorType, usingDirective.GetLocation(), symbolString));
83-
}
84-
else
85-
{
86-
context.ReportDiagnostic(Diagnostic.Create(DescriptorNamespace, usingDirective.GetLocation(), symbolString));
87-
}
88-
}
107+
context.ReportDiagnostic(Diagnostic.Create(DescriptorType, usingDirective.GetLocation(), symbolString));
89108
}
109+
110+
break;
90111
}
91112
}
92113
}

0 commit comments

Comments
 (0)