-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(analyzers): add rule for disallowing readonly members with Provi…
…de or Query (UIC003)
- Loading branch information
Showing
15 changed files
with
318 additions
and
14 deletions.
There are no files selected for viewing
Binary file modified
BIN
+3 KB
(120%)
Assets/UIComponents/Roslyn/UIComponents.Roslyn.Analyzers.CodeFixes.dll
Binary file not shown.
Binary file modified
BIN
+772 Bytes
(110%)
Assets/UIComponents/Roslyn/UIComponents.Roslyn.Analyzers.CodeFixes.pdb
Binary file not shown.
Binary file modified
BIN
+1.5 KB
(110%)
Assets/UIComponents/Roslyn/UIComponents.Roslyn.Analyzers.dll
Binary file not shown.
Binary file modified
BIN
+640 Bytes
(110%)
Assets/UIComponents/Roslyn/UIComponents.Roslyn.Analyzers.pdb
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified
BIN
+0 Bytes
(100%)
Assets/UIComponents/Roslyn/UIComponents.Roslyn.Generation.dll
Binary file not shown.
Binary file modified
BIN
+0 Bytes
(100%)
Assets/UIComponents/Roslyn/UIComponents.Roslyn.Generation.pdb
Binary file not shown.
20 changes: 13 additions & 7 deletions
20
...nts.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers.CodeFixes/CodeFixResources.Designer.cs
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
...oslyn.Analyzers/UIComponents.Roslyn.Analyzers.CodeFixes/InvalidReadonlyCodeFixProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Editing; | ||
using System.Collections.Immutable; | ||
using System.Composition; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using System.Threading; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
|
||
namespace UIComponents.Roslyn.Analyzers | ||
{ | ||
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(InvalidReadonlyCodeFixProvider)), Shared] | ||
public sealed class InvalidReadonlyCodeFixProvider : CodeFixProvider | ||
{ | ||
public sealed override ImmutableArray<string> FixableDiagnosticIds | ||
{ | ||
get { return ImmutableArray.Create(InvalidReadonlyMemberAnalyzer.DiagnosticId); } | ||
} | ||
|
||
public sealed override FixAllProvider GetFixAllProvider() | ||
{ | ||
return WellKnownFixAllProviders.BatchFixer; | ||
} | ||
|
||
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
|
||
var diagnostic = context.Diagnostics.First(); | ||
var diagnosticSpan = diagnostic.Location.SourceSpan; | ||
|
||
var fieldDeclaration = root.FindToken(diagnosticSpan.Start).Parent | ||
.AncestorsAndSelf() | ||
.OfType<FieldDeclarationSyntax>() | ||
.First(); | ||
|
||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
title: CodeFixResources.InvalidReadonlyFix_Title, | ||
createChangedDocument: cancellationToken => RemoveReadonly(context.Document, fieldDeclaration, cancellationToken), | ||
equivalenceKey: nameof(CodeFixResources.InvalidReadonlyFix_Title)), | ||
diagnostic); | ||
} | ||
|
||
private async Task<Document> RemoveReadonly(Document document, FieldDeclarationSyntax fieldDeclaration, CancellationToken cancellationToken) | ||
{ | ||
var typeDeclaration = fieldDeclaration | ||
.Ancestors() | ||
.OfType<BaseTypeDeclarationSyntax>() | ||
.First(); | ||
|
||
var editor = new SyntaxEditor(typeDeclaration, document.Project.Solution.Workspace); | ||
|
||
var newModifiers = fieldDeclaration.Modifiers | ||
.Where(modifier => modifier.Kind() != SyntaxKind.ReadOnlyKeyword); | ||
var newFieldDeclaration = fieldDeclaration.WithModifiers(new SyntaxTokenList(newModifiers)); | ||
editor.ReplaceNode(fieldDeclaration, newFieldDeclaration); | ||
|
||
var newDeclaration = editor.GetChangedRoot(); | ||
|
||
var root = await document.GetSyntaxRootAsync(cancellationToken); | ||
var newRoot = root.ReplaceNode(typeDeclaration, newDeclaration); | ||
var newDocument = document.WithSyntaxRoot(newRoot); | ||
|
||
return newDocument; | ||
} | ||
} | ||
} |
106 changes: 106 additions & 0 deletions
106
...Roslyn.Analyzers/UIComponents.Roslyn.Analyzers.Test/InvalidReadonlyMemberAnalyzerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
using Microsoft.CodeAnalysis.Testing; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using System.Threading.Tasks; | ||
using VerifyCS = UIComponents.Roslyn.Analyzers.Test.CSharpCodeFixVerifier< | ||
UIComponents.Roslyn.Analyzers.InvalidReadonlyMemberAnalyzer, | ||
UIComponents.Roslyn.Analyzers.InvalidReadonlyCodeFixProvider>; | ||
|
||
namespace UIComponents.Roslyn.Analyzers.Test | ||
{ | ||
[TestClass] | ||
public class InvalidReadonlyMemberAnalyzerTests | ||
{ | ||
private const string UIComponentsDefinition = @" | ||
namespace UIComponents | ||
{ | ||
public class ProvideAttribute : Attribute | ||
{ | ||
public ProvideAttribute() {} | ||
} | ||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] | ||
public class QueryAttribute : Attribute | ||
{ | ||
public QueryAttribute(string id) {} | ||
} | ||
} | ||
"; | ||
|
||
[TestMethod] | ||
public async Task It_Reports_Readonly_Query_And_Provide_Members() | ||
{ | ||
var test = $@" | ||
using System; | ||
{UIComponentsDefinition} | ||
namespace Application | ||
{{ | ||
public class OtherAttribute : Attribute {{}} | ||
public class TestComponent | ||
{{ | ||
{{|#0:[UIComponents.Provide] | ||
public readonly string first;|}} | ||
{{|#1:[Other, UIComponents.Query(""test"")] | ||
[UIComponents.Query(""test2"")] | ||
public readonly string second;|}} | ||
}} | ||
}}"; | ||
|
||
var fixtest = $@" | ||
using System; | ||
{UIComponentsDefinition} | ||
namespace Application | ||
{{ | ||
public class OtherAttribute : Attribute {{}} | ||
public class TestComponent | ||
{{ | ||
[UIComponents.Provide] | ||
public string first; | ||
[Other, UIComponents.Query(""test"")] | ||
[UIComponents.Query(""test2"")] | ||
public string second; | ||
}} | ||
}}"; | ||
var firstResult = VerifyCS.Diagnostic("UIC003") | ||
.WithLocation(0) | ||
.WithArguments("ProvideAttribute"); | ||
var secondResult = VerifyCS.Diagnostic("UIC003") | ||
.WithLocation(1) | ||
.WithArguments("QueryAttribute"); | ||
await VerifyCS.VerifyCodeFixAsync(test, | ||
new DiagnosticResult[] { firstResult, secondResult }, | ||
fixtest | ||
); | ||
} | ||
|
||
[TestMethod] | ||
public async Task It_Does_Not_Report_Non_Readonly_Fields() | ||
{ | ||
var test = $@" | ||
using System; | ||
{UIComponentsDefinition} | ||
namespace Application | ||
{{ | ||
public class OtherAttribute : Attribute {{}} | ||
public class TestComponent | ||
{{ | ||
[UIComponents.Provide] | ||
public string first; | ||
[Other, UIComponents.Query(""test"")] | ||
public string second; | ||
}} | ||
}}"; | ||
await VerifyCS.VerifyAnalyzerAsync(test); | ||
} | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
...omponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/InvalidReadonlyMemberAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
|
||
namespace UIComponents.Roslyn.Analyzers | ||
{ | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public sealed class InvalidReadonlyMemberAnalyzer : DiagnosticAnalyzer | ||
{ | ||
public const string DiagnosticId = "UIC003"; | ||
|
||
private static readonly LocalizableString Title = | ||
new LocalizableResourceString(nameof(Resources.UIC003_Title), Resources.ResourceManager, typeof(Resources)); | ||
private static readonly LocalizableString MessageFormat = | ||
new LocalizableResourceString(nameof(Resources.UIC003_MessageFormat), Resources.ResourceManager, typeof(Resources)); | ||
private static readonly LocalizableString Description = | ||
new LocalizableResourceString(nameof(Resources.UIC003_Description), Resources.ResourceManager, typeof(Resources)); | ||
private static readonly string Category = "Core"; | ||
|
||
private static readonly DiagnosticDescriptor Rule = | ||
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics | ||
{ | ||
get { return ImmutableArray.Create(Rule); } | ||
} | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
context.EnableConcurrentExecution(); | ||
|
||
context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.FieldDeclaration); | ||
} | ||
|
||
private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) | ||
{ | ||
var fieldDeclaration = (FieldDeclarationSyntax)context.Node; | ||
|
||
if (!fieldDeclaration.Modifiers.Any(SyntaxKind.ReadOnlyKeyword)) | ||
return; | ||
|
||
var provideAttributeSymbol = | ||
context.Compilation.GetTypeByMetadataName("UIComponents.ProvideAttribute"); | ||
var queryAttributeSymbol = | ||
context.Compilation.GetTypeByMetadataName("UIComponents.QueryAttribute"); | ||
|
||
var attributeLists = fieldDeclaration.AttributeLists; | ||
|
||
foreach (var attributeList in attributeLists) | ||
{ | ||
var found = false; | ||
|
||
foreach (var attribute in attributeList.Attributes) | ||
{ | ||
var symbol = context.SemanticModel.GetSymbolInfo(attribute).Symbol; | ||
|
||
if (symbol == null) | ||
continue; | ||
|
||
var typeSymbol = symbol.ContainingType; | ||
|
||
if (typeSymbol == null) | ||
continue; | ||
|
||
if (!SymbolEqualityComparer.Default.Equals(typeSymbol, provideAttributeSymbol) | ||
&& !SymbolEqualityComparer.Default.Equals(typeSymbol, queryAttributeSymbol)) | ||
continue; | ||
|
||
var diagnostic = Diagnostic.Create(Rule, fieldDeclaration.GetLocation(), typeSymbol.Name); | ||
context.ReportDiagnostic(diagnostic); | ||
found = true; | ||
|
||
break; | ||
} | ||
|
||
if (found) | ||
break; | ||
} | ||
} | ||
} | ||
} |
38 changes: 31 additions & 7 deletions
38
....Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/Resources.Designer.cs
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters