Skip to content

Commit 21c28d2

Browse files
0x084Evweijsters
authored andcommitted
added SA1135UsingDirectivesMustBeQualified
1 parent d18b15f commit 21c28d2

7 files changed

Lines changed: 352 additions & 2 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.ReadabilityRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Composition;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Microsoft.CodeAnalysis;
12+
using Microsoft.CodeAnalysis.CodeActions;
13+
using Microsoft.CodeAnalysis.CodeFixes;
14+
using Microsoft.CodeAnalysis.CSharp;
15+
using Microsoft.CodeAnalysis.CSharp.Syntax;
16+
using StyleCop.Analyzers.Helpers;
17+
18+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1135CodeFixProvider))]
19+
[Shared]
20+
internal class SA1135CodeFixProvider : CodeFixProvider
21+
{
22+
/// <inheritdoc/>
23+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
24+
ImmutableArray.Create(SA1135UsingDirectivesMustBeQualified.DiagnosticId);
25+
26+
/// <inheritdoc/>
27+
public override FixAllProvider GetFixAllProvider()
28+
{
29+
return FixAll.Instance;
30+
}
31+
32+
/// <inheritdoc/>
33+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
34+
{
35+
foreach (var diagnostic in context.Diagnostics)
36+
{
37+
context.RegisterCodeFix(
38+
CodeAction.Create(
39+
ReadabilityResources.SA1135CodeFix,
40+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
41+
nameof(SA1135CodeFixProvider)),
42+
diagnostic);
43+
}
44+
45+
return SpecializedTasks.CompletedTask;
46+
}
47+
48+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
49+
{
50+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
51+
52+
var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan) as UsingDirectiveSyntax;
53+
if (node == null)
54+
{
55+
return document;
56+
}
57+
58+
SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
59+
var replacementNode = GenerateReplacementNode(semanticModel, node, cancellationToken);
60+
var newSyntaxRoot = syntaxRoot.ReplaceNode(node, replacementNode);
61+
return document.WithSyntaxRoot(newSyntaxRoot);
62+
}
63+
64+
private static SyntaxNode GenerateReplacementNode(SemanticModel semanticModel, UsingDirectiveSyntax node, CancellationToken cancellationToken)
65+
{
66+
SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(node.Name, cancellationToken);
67+
return node.WithName(SyntaxFactory.ParseName(symbolInfo.Symbol.ToString()));
68+
}
69+
70+
private class FixAll : DocumentBasedFixAllProvider
71+
{
72+
public static FixAllProvider Instance { get; } =
73+
new FixAll();
74+
75+
protected override string CodeActionTitle =>
76+
ReadabilityResources.SA1135CodeFix;
77+
78+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
79+
{
80+
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
81+
if (diagnostics.IsEmpty)
82+
{
83+
return null;
84+
}
85+
86+
SyntaxNode syntaxRoot = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
87+
SemanticModel semanticModel = await document.GetSemanticModelAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
88+
89+
var replaceMap = new Dictionary<SyntaxNode, SyntaxNode>();
90+
91+
foreach (Diagnostic diagnostic in diagnostics)
92+
{
93+
var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan, false, true) as UsingDirectiveSyntax;
94+
if (node == null || node.IsMissing)
95+
{
96+
continue;
97+
}
98+
99+
replaceMap[node] = GenerateReplacementNode(semanticModel, node, fixAllContext.CancellationToken);
100+
}
101+
102+
return syntaxRoot.ReplaceNodes(replaceMap.Keys, (originalNode, rewrittenNode) => replaceMap[originalNode]);
103+
}
104+
}
105+
}
106+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Test.ReadabilityRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Analyzers.ReadabilityRules;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.CodeFixes;
12+
using Microsoft.CodeAnalysis.Diagnostics;
13+
using TestHelper;
14+
using Xunit;
15+
16+
public class SA1135UnitTests : CodeFixVerifier
17+
{
18+
[Fact]
19+
public async Task TestUnqualifiedUsingsAsync()
20+
{
21+
const string testCode = @"
22+
namespace System.Threading
23+
{
24+
using IO;
25+
using Tasks;
26+
}";
27+
const string fixedCode = @"
28+
namespace System.Threading
29+
{
30+
using System.IO;
31+
using System.Threading.Tasks;
32+
}";
33+
34+
DiagnosticResult[] expected =
35+
{
36+
this.CSharpDiagnostic().WithLocation(4, 5),
37+
this.CSharpDiagnostic().WithLocation(5, 5)
38+
};
39+
40+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
41+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
42+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
43+
}
44+
45+
protected override CodeFixProvider GetCSharpCodeFixProvider()
46+
{
47+
return new SA1135CodeFixProvider();
48+
}
49+
50+
/// <inheritdoc/>
51+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
52+
{
53+
yield return new SA1135UsingDirectivesMustBeQualified();
54+
}
55+
}
56+
}

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/ReadabilityResources.Designer.cs

Lines changed: 39 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/ReadabilityResources.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,18 @@
522522
<data name="SA1139CodeFix" xml:space="preserve">
523523
<value>Use literal suffix notation instead of casting</value>
524524
</data>
525+
<data name="SA1135CodeFix" xml:space="preserve">
526+
<value>Qualify using directive</value>
527+
</data>
528+
<data name="SA1135Description" xml:space="preserve">
529+
<value>All using directives must be qualified.</value>
530+
</data>
531+
<data name="SA1135MessageFormat" xml:space="preserve">
532+
<value>The using directive '{0}' must be qualified to '{1}'</value>
533+
</data>
534+
<data name="SA1135Title" xml:space="preserve">
535+
<value>Using directives must be qualified</value>
536+
</data>
525537
<data name="SX1101CodeFix" xml:space="preserve">
526538
<value>Remove 'this.' prefix</value>
527539
</data>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.ReadabilityRules
5+
{
6+
using System.Collections.Immutable;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
12+
/// <summary>
13+
/// A using directive is not qualified.
14+
/// </summary>
15+
/// <remarks>
16+
/// <para>
17+
/// A violation of this rule occurs when a using directive is contained within a namespace and is not qualified.
18+
/// </para>
19+
/// </remarks>
20+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
21+
public class SA1135UsingDirectivesMustBeQualified : DiagnosticAnalyzer
22+
{
23+
/// <summary>
24+
/// The ID for diagnostics produced by the <see cref="SA1135UsingDirectivesMustBeQualified"/> analyzer.
25+
/// </summary>
26+
public const string DiagnosticId = "SA1135";
27+
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(ReadabilityResources.SA1135Title), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
28+
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(ReadabilityResources.SA1135MessageFormat), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
29+
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(ReadabilityResources.SA1135Description), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
30+
private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1135.md";
31+
32+
private static readonly DiagnosticDescriptor Descriptor =
33+
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.OrderingRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
34+
35+
private static readonly ImmutableArray<DiagnosticDescriptor> SupportedDiagnosticsValue =
36+
ImmutableArray.Create(Descriptor);
37+
38+
/// <inheritdoc/>
39+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => SupportedDiagnosticsValue;
40+
41+
/// <inheritdoc/>
42+
public override void Initialize(AnalysisContext context)
43+
{
44+
context.RegisterCompilationStartAction(HandleCompilationStart);
45+
}
46+
47+
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
48+
{
49+
context.RegisterSyntaxNodeActionHonorExclusions(HandleNamespaceDeclaration, SyntaxKind.NamespaceDeclaration);
50+
}
51+
52+
private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context)
53+
{
54+
var namespaceDirective = (NamespaceDeclarationSyntax)context.Node;
55+
CheckUsingDeclarations(context, namespaceDirective.Usings);
56+
}
57+
58+
private static void CheckUsingDeclarations(SyntaxNodeAnalysisContext context, SyntaxList<UsingDirectiveSyntax> usingDirectives)
59+
{
60+
foreach (var usingDirective in usingDirectives)
61+
{
62+
if (usingDirective.Alias == null && usingDirective.StaticKeyword.IsKind(SyntaxKind.None))
63+
{
64+
SymbolInfo symbolInfo = context.SemanticModel.GetSymbolInfo(usingDirective.Name, context.CancellationToken);
65+
if (symbolInfo.Symbol != null && symbolInfo.Symbol.Kind == SymbolKind.Namespace)
66+
{
67+
string symbolString = symbolInfo.Symbol.ToString();
68+
string usingString = usingDirective.Name.ToString();
69+
70+
if (symbolString != usingString)
71+
{
72+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, usingDirective.GetLocation(), new[] { usingString, symbolString }));
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}
79+
}

StyleCopAnalyzers.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "documentation", "documentat
115115
documentation\SA1132.md = documentation\SA1132.md
116116
documentation\SA1133.md = documentation\SA1133.md
117117
documentation\SA1134.md = documentation\SA1134.md
118+
documentation\SA1135.md = documentation\SA1135.md
118119
documentation\SA1136.md = documentation\SA1136.md
119120
documentation\SA1137.md = documentation\SA1137.md
120121
documentation\SA1139.md = documentation\SA1139.md

0 commit comments

Comments
 (0)