Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add analyzer rule for disallowing readonly fields with QueryAttribute or ProvideAttribute #111

Merged
merged 4 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-it.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ plugins:
ignoreRecommendedBump: true

hooks:
after:bump: ["node ./Scripts/update_snapshot_versions.js ${version}", "node ./Scripts/update_package_version.js ${version}", "node ./Scripts/generate_unitypackage.js v${version}"]
after:bump: ["node ./Scripts/update_package_version.js ${version}", "node ./Scripts/update_snapshot_versions.js ${version}", "node ./Scripts/generate_unitypackage.js v${version}"]
Binary file not shown.
Binary file not shown.
Binary file modified Assets/UIComponents/Roslyn/UIComponents.Roslyn.Analyzers.dll
Binary file not shown.
Binary file modified Assets/UIComponents/Roslyn/UIComponents.Roslyn.Analyzers.pdb
Binary file not shown.
Binary file modified Assets/UIComponents/Roslyn/UIComponents.Roslyn.Common.dll
Binary file not shown.
Binary file modified Assets/UIComponents/Roslyn/UIComponents.Roslyn.Common.pdb
Binary file not shown.
Binary file modified Assets/UIComponents/Roslyn/UIComponents.Roslyn.Generation.dll
Binary file not shown.
Binary file modified Assets/UIComponents/Roslyn/UIComponents.Roslyn.Generation.pdb
Binary file not shown.
4 changes: 2 additions & 2 deletions Packages/manifest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"dependencies": {
"com.unity.addressables": "1.18.19",
"com.unity.ide.rider": "3.0.24",
"com.unity.ide.visualstudio": "2.0.18",
"com.unity.ide.rider": "3.0.25",
"com.unity.ide.visualstudio": "2.0.21",
"com.unity.ide.vscode": "1.2.5",
"com.unity.test-framework": "1.1.33",
"com.unity.test-framework.performance": "3.0.0-pre.2",
Expand Down
4 changes: 2 additions & 2 deletions Packages/packages-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"url": "https://packages.unity.com"
},
"com.unity.ide.rider": {
"version": "3.0.24",
"version": "3.0.25",
"depth": 0,
"source": "registry",
"dependencies": {
Expand All @@ -31,7 +31,7 @@
"url": "https://packages.unity.com"
},
"com.unity.ide.visualstudio": {
"version": "2.0.18",
"version": "2.0.21",
"depth": 0,
"source": "registry",
"dependencies": {
Expand Down
28 changes: 26 additions & 2 deletions Scripts/update_package_version.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// update_package_version.js
//
// This script updates the version in the package's package.json file,
// as well as all occurrences of it in the README.md file.
// as well as all occurrences of it in the README.md file and Roslyn projects.
//

const path = require('path');
Expand Down Expand Up @@ -50,8 +50,32 @@ function replaceVersionInFile(filePath) {
}

const readmePath = path.resolve(__dirname, '..', 'README.md');
const roslynSolutionPath = path.resolve(__dirname, '..', 'UIComponents.Roslyn', 'UIComponents.Roslyn.sln');

/**
* @param {string} startDir
* @returns {string[]}
*/
function findCsprojFiles(currentDir) {
let csprojFiles = [];
const files = fs.readdirSync(currentDir);

for (const file of files) {
const filePath = path.join(currentDir, file);
const isDirectory = fs.statSync(filePath).isDirectory();

if (isDirectory) {
csprojFiles = csprojFiles.concat(findCsprojFiles(filePath));
} else if (file.endsWith('.csproj')) {
csprojFiles.push(filePath);
}
}

return csprojFiles;
}

const pathsToReplace = [readmePath];
const roslynProjectPaths = findCsprojFiles(path.resolve(__dirname, '..', 'UIComponents.Roslyn'));
const pathsToReplace = [readmePath, roslynSolutionPath, ...roslynProjectPaths];

for (const path of pathsToReplace) {
replaceVersionInFile(path);
Expand Down

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
Expand Up @@ -123,4 +123,7 @@
<data name="PartialFix_Title" xml:space="preserve">
<value>Make class partial</value>
</data>
<data name="InvalidReadonlyFix_Title" xml:space="preserve">
<value>Remove readonly keyword</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
diagnostic);
}

private async Task<Document> RemoveAttribute(Document document, AttributeSyntax attributeSyntax, CancellationToken cancellationToken)
private async Task<Document> RemoveAttribute(Document document, AttributeSyntax attributeSyntax, CancellationToken cancellationToken)
{
var attributeList = attributeSyntax.Parent as AttributeListSyntax;
var classDeclaration = attributeList.Parent;
Expand Down
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
Expand Up @@ -6,6 +6,7 @@
<IncludeBuildOutput>false</IncludeBuildOutput>
<IsRoslynComponent>true</IsRoslynComponent>
<RootNamespace>UIComponents.Roslyn.Analyzers</RootNamespace>
<ReleaseVersion>1.0.0-beta.7</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<IncludeBuildOutput>false</IncludeBuildOutput>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<ReleaseVersion>1.0.0-beta.7</ReleaseVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
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
Expand Up @@ -5,6 +5,7 @@

<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<ReleaseVersion>1.0.0-beta.7</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
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

View workflow job for this annotation

GitHub Actions / Test on 2020.3.48f1 (StandaloneLinux64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2020.3.48f1 (StandaloneLinux64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2020.3.48f1 (StandaloneWindows64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2020.3.48f1 (StandaloneWindows64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2021.3.29f1 (StandaloneLinux64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2021.3.29f1 (StandaloneLinux64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2021.3.29f1 (StandaloneWindows64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2021.3.29f1 (StandaloneWindows64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2022.3.7f1 (StandaloneLinux64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2022.3.7f1 (StandaloneLinux64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2022.3.7f1 (StandaloneWindows64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2022.3.7f1 (StandaloneWindows64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2023.1.8f1 (StandaloneLinux64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2023.1.8f1 (StandaloneLinux64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2023.1.8f1 (StandaloneWindows64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 4 in UIComponents.Roslyn/UIComponents.Roslyn.Analyzers/UIComponents.Roslyn.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / Test on 2023.1.8f1 (StandaloneWindows64)

Analyzer release file 'AnalyzerReleases.Shipped.md' has a missing or invalid release header '## Release 1.0.0-alpha.6'. (https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

### 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
Loading
Loading