Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
94c5cab
commit b9ae772
Showing
8 changed files
with
294 additions
and
3 deletions.
There are no files selected for viewing
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
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
58 changes: 58 additions & 0 deletions
58
src/Orleans.Analyzers/AlwaysInterleaveDiagnosticAnalyzer.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,58 @@ | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
namespace Orleans.Analyzers | ||
{ | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class AlwaysInterleaveDiagnosticAnalyzer : DiagnosticAnalyzer | ||
{ | ||
private const string AlwaysInterleaveAttributeName = "Orleans.Concurrency.AlwaysInterleaveAttribute"; | ||
|
||
public const string DiagnosticId = "ORLEANS0001"; | ||
public const string Title = "[AlwaysInterleave] must only be used on the grain interface method and not the grain class method."; | ||
public const string MessageFormat = Title; | ||
public const string Category = "Usage"; | ||
|
||
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.RegisterSyntaxNodeAction( | ||
AnalyzeSyntax, | ||
SyntaxKind.MethodDeclaration); | ||
} | ||
|
||
private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context) | ||
{ | ||
var alwaysInterleaveAttribute = context.Compilation.GetTypeByMetadataName(AlwaysInterleaveAttributeName); | ||
|
||
var syntax = (MethodDeclarationSyntax)context.Node; | ||
var symbol = context.SemanticModel.GetDeclaredSymbol(syntax); | ||
|
||
if (symbol.ContainingType.TypeKind == TypeKind.Interface) | ||
{ | ||
// TODO: Check that interface inherits from IGrain | ||
return; | ||
} | ||
|
||
foreach (var attribute in symbol.GetAttributes()) | ||
{ | ||
if (!attribute.AttributeClass.Equals(alwaysInterleaveAttribute)) | ||
{ | ||
return; | ||
} | ||
|
||
var syntaxReference = attribute.ApplicationSyntaxReference; | ||
|
||
context.ReportDiagnostic( | ||
Diagnostic.Create(Rule, Location.Create(syntaxReference.SyntaxTree, syntaxReference.Span))); | ||
} | ||
} | ||
} | ||
} |
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,13 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<PackageId>Microsoft.Orleans.Analyzers</PackageId> | ||
<Title>Microsoft Orleans Analyzers</Title> | ||
<Description>C# Analyzers for Microsoft Orleans.</Description> | ||
<TargetFrameworks>netstandard2.0</TargetFrameworks> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion)" PrivateAssets="all" /> | ||
</ItemGroup> | ||
|
||
</Project> |
58 changes: 58 additions & 0 deletions
58
test/Analyzers.Tests/AlwaysInterleaveDiagnosticAnalyzerTests.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,58 @@ | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using Orleans.Analyzers; | ||
using Xunit; | ||
|
||
namespace Analyzers.Tests | ||
{ | ||
[TestCategory("BVT"), TestCategory("Analyzer")] | ||
public class AlwaysInterleaveDiagnosticAnalyzerTest : DiagnosticAnalyzerTestBase<AlwaysInterleaveDiagnosticAnalyzer> | ||
{ | ||
protected override Task<(Diagnostic[], string)> GetDiagnosticsAsync(string source, params string[] extraUsings) | ||
=> base.GetDiagnosticsAsync(source, extraUsings.Concat(new[] { "Orleans.Concurrency" }).ToArray()); | ||
|
||
[Fact] | ||
public async Task AlwaysInterleave_Analyzer_NoWarningsIfAttributeIsNotUsed() => await this.AssertNoDiagnostics(@" | ||
class C | ||
{ | ||
Task M() => Task.CompletedTask; | ||
} | ||
"); | ||
|
||
[Fact] | ||
public async Task AlwaysInterleave_Analyzer_NoWarningsIfAttributeIsUsedOnInterface() => await this.AssertNoDiagnostics(@" | ||
public interface I : IGrain | ||
{ | ||
[AlwaysInterleave] | ||
Task<string> M(); | ||
} | ||
"); | ||
|
||
[Fact] | ||
public async Task AlwaysInterleave_Analyzer_WarningIfAttributeisUsedOnGrainClass() | ||
{ | ||
var (diagnostics, source) = await this.GetDiagnosticsAsync(@" | ||
public interface I : IGrain | ||
{ | ||
Task<int> Method(); | ||
} | ||
public class C : I | ||
{ | ||
[AlwaysInterleave] | ||
public Task<int> Method() => Task.FromResult(0); | ||
} | ||
"); | ||
|
||
var diagnostic = diagnostics.Single(); | ||
|
||
Assert.Equal(AlwaysInterleaveDiagnosticAnalyzer.DiagnosticId, diagnostic.Id); | ||
Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity); | ||
Assert.Equal(AlwaysInterleaveDiagnosticAnalyzer.MessageFormat, diagnostic.GetMessage()); | ||
|
||
var span = diagnostic.Location.SourceSpan; | ||
Assert.Equal("AlwaysInterleave", source.Substring(span.Start, span.End - span.Start)); | ||
} | ||
} | ||
} |
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,22 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="xunit" Version="$(xUnitVersion)" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion)" /> | ||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(MicrosoftExtensionsDependencyModelVersion)" /> | ||
<DotNetCliToolReference Include="dotnet-xunit" Version="$(xUnitVersion)" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="$(SourceRoot)src\Orleans.Analyzers\Orleans.Analyzers.csproj" /> | ||
<ProjectReference Include="$(SourceRoot)src\Orleans.Core.Abstractions\Orleans.Core.Abstractions.csproj" /> | ||
<ProjectReference Include="$(SourceRoot)test\TestInfrastructure\TestExtensions\TestExtensions.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Include="Analyzers.Tests.xunit.runner.json"> | ||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||
</None> | ||
</ItemGroup> | ||
|
||
</Project> |
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,8 @@ | ||
{ | ||
"appDomain": "ifAvailable", | ||
"diagnosticMessages": true, | ||
"parallelizeAssembly": false, | ||
"parallelizeTestCollections": false, | ||
"methodDisplay": "classAndMethod", | ||
"shadowCopy": false | ||
} |
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,101 @@ | ||
// Derived from Entity Framework Core Analyzer Tests | ||
// https://github.com/aspnet/EntityFrameworkCore/blob/cbefe76162b16c1122629dbf6c85becbbb5cdc8e/test/EFCore.Analyzers.Tests/TestUtilities/DiagnosticAnalyzerTestBase.cs | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Text; | ||
using Microsoft.Extensions.DependencyModel; | ||
using Xunit; | ||
|
||
namespace Analyzers.Tests | ||
{ | ||
public abstract class DiagnosticAnalyzerTestBase<TDiagnosticAnalyzer> | ||
where TDiagnosticAnalyzer : DiagnosticAnalyzer, new() | ||
{ | ||
private static readonly string[] Usings = new[] { | ||
"System", | ||
"System.Threading.Tasks", | ||
"Orleans" | ||
}; | ||
|
||
protected virtual DiagnosticAnalyzer CreateDiagnosticAnalyzer() => new TDiagnosticAnalyzer(); | ||
|
||
protected async Task AssertNoDiagnostics(string source, params string[] extraUsings) | ||
{ | ||
var (diagnostics, _) = await this.GetDiagnosticsAsync(source, extraUsings); | ||
Assert.Empty(diagnostics); | ||
} | ||
|
||
protected virtual async Task<(Diagnostic[], string)> GetDiagnosticsAsync(string source, params string[] extraUsings) | ||
{ | ||
var sb = new StringBuilder(); | ||
foreach (var @using in Usings.Concat(extraUsings)) | ||
{ | ||
sb.AppendLine($"using {@using};"); | ||
} | ||
sb.AppendLine(source); | ||
|
||
var sourceText = sb.ToString(); | ||
return (await this.GetDiagnosticsFullSourceAsync(sourceText), sourceText); | ||
} | ||
|
||
protected async Task<Diagnostic[]> GetDiagnosticsFullSourceAsync(string source) | ||
{ | ||
var compilation = await CreateProject(source).GetCompilationAsync(); | ||
var errors = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error); | ||
|
||
Assert.Empty(errors); | ||
|
||
var analyzer = this.CreateDiagnosticAnalyzer(); | ||
var compilationWithAnalyzers | ||
= compilation | ||
.WithOptions( | ||
compilation.Options.WithSpecificDiagnosticOptions( | ||
analyzer.SupportedDiagnostics.ToDictionary(d => d.Id, d => ReportDiagnostic.Default))) | ||
.WithAnalyzers(ImmutableArray.Create(analyzer)); | ||
|
||
var diagnostics = await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync(); | ||
|
||
return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); | ||
} | ||
|
||
private static Project CreateProject(string source) | ||
{ | ||
const string fileName = "Test.cs"; | ||
|
||
var projectId = ProjectId.CreateNewId(debugName: "TestProject"); | ||
var documentId = DocumentId.CreateNewId(projectId, fileName); | ||
|
||
var assemblies = new[] | ||
{ | ||
typeof(Task).Assembly, | ||
typeof(Orleans.Grain).Assembly | ||
}; | ||
|
||
var metadataReferences = assemblies | ||
.SelectMany(x => x.GetReferencedAssemblies()) | ||
.Select(Assembly.Load) | ||
.Concat(assemblies) | ||
.Select(x => MetadataReference.CreateFromFile(x.Location)) | ||
.Cast<MetadataReference>() | ||
.ToList(); | ||
|
||
var solution = new AdhocWorkspace() | ||
.CurrentSolution | ||
.AddProject(projectId, "TestProject", "TestProject", LanguageNames.CSharp) | ||
.AddMetadataReferences(projectId, metadataReferences) | ||
.AddDocument(documentId, fileName, SourceText.From(source)); | ||
|
||
return solution.GetProject(projectId) | ||
.WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); | ||
} | ||
} | ||
} |