-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
Add analyzer rule for disallowing readonly fields with QueryAttribute or ProvideAttribute
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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; | ||
} | ||
} | ||
} |
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); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
; Shipped analyzer releases | ||
; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md | ||
|
||
## Release 1.0.0-alpha.6 | ||
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2020.3.48f1 (StandaloneLinux64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2020.3.48f1 (StandaloneLinux64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2020.3.48f1 (StandaloneWindows64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2020.3.48f1 (StandaloneWindows64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2021.3.29f1 (StandaloneLinux64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2021.3.29f1 (StandaloneLinux64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2021.3.29f1 (StandaloneWindows64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2021.3.29f1 (StandaloneWindows64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2022.3.7f1 (StandaloneLinux64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2022.3.7f1 (StandaloneLinux64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2022.3.7f1 (StandaloneWindows64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2022.3.7f1 (StandaloneWindows64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2023.1.8f1 (StandaloneLinux64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2023.1.8f1 (StandaloneLinux64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2023.1.8f1 (StandaloneWindows64)
Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md GitHub Actions / Test on 2023.1.8f1 (StandaloneWindows64)
|
||
|
||
### New Rules | ||
|
||
Rule ID | Category | Severity | Notes | ||
---------|----------|----------|---------------------------------------- | ||
UIC001 | Core | Error | UIComponentPartialAnalyzer | ||
UIC002 | Core | Warning | UIComponentInternalMethodAnalyzer | ||
UIC101 | Layout | Warning | UIComponentIdenticalStylesheetAnalyzer | ||
UIC102 | Layout | Error | EmptyUxmlNameAnalyzer | ||
UIC103 | Layout | Error | EmptyUxmlTraitNameAnalyzer |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
### New Rules | ||
Rule ID | Category | Severity | Notes | ||
--------|----------|----------|------- | ||
UIC103 | Category | Error | EmptyUxmlTraitNameAnalyzer |