Skip to content

Commit ab29ee8

Browse files
authored
Analyzer: Added Dependency Injection Analyzer (#2215)
1 parent 7ac99a4 commit ab29ee8

File tree

13 files changed

+374
-49
lines changed

13 files changed

+374
-49
lines changed

src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/FunctionAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ private static bool TryGetInputTypeFromContext(SemanticModel semanticModel, Synt
297297

298298
private static bool TryGetDurableActivityContextExpression(SemanticModel semanticModel, SyntaxNode node, out SyntaxNode durableContextExpression)
299299
{
300-
if (SyntaxNodeUtils.TryGetMethodDeclaration(node, out SyntaxNode methodDeclaration))
300+
if (SyntaxNodeUtils.TryGetMethodDeclaration(node, out MethodDeclarationSyntax methodDeclaration))
301301
{
302302
var memberAccessExpressionList = methodDeclaration.DescendantNodes().Where(x => x.IsKind(SyntaxKind.SimpleMemberAccessExpression));
303303
foreach (var memberAccessExpression in memberAccessExpressionList)

src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Entity/DispatchEntityNameAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ private void AnalyzeDispatchAndFindMethodDeclarations(SyntaxNodeAnalysisContext
6262
var name = expression.Name;
6363
if (name.ToString().StartsWith("DispatchAsync"))
6464
{
65-
if(SyntaxNodeUtils.TryGetMethodDeclaration(expression, out SyntaxNode methodDeclaration))
65+
if(SyntaxNodeUtils.TryGetMethodDeclaration(expression, out MethodDeclarationSyntax methodDeclaration))
6666
{
6767
methodDeclarations.Add(methodDeclaration);
6868
}

src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Entity/StaticFunctionAnalyzer.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ public class StaticFunctionAnalyzer
2222

2323
public static void ReportProblems(CompilationAnalysisContext context, SyntaxNode methodDeclaration)
2424
{
25-
var staticKeyword = methodDeclaration.ChildTokens().FirstOrDefault(x => x.IsKind(SyntaxKind.StaticKeyword));
26-
if (staticKeyword == null || staticKeyword.IsKind(SyntaxKind.None))
25+
if (!SyntaxNodeUtils.IsInStaticMethod(methodDeclaration))
2726
{
2827
SemanticModel semanticModel = context.Compilation.GetSemanticModel(methodDeclaration.SyntaxTree);
2928
if (IsInEntityClass(semanticModel, methodDeclaration))
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CSharp;
8+
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
11+
namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers
12+
{
13+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
14+
public class DependencyInjectionAnalyzer
15+
{
16+
public const string DiagnosticId = "DF0113";
17+
18+
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.DependencyInjectionAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
19+
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.DeterministicAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
20+
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.DeterministicAnalyzerDescription), Resources.ResourceManager, typeof(Resources));
21+
private const string Category = SupportedCategories.Orchestrator;
22+
public const DiagnosticSeverity Severity = DiagnosticSeverity.Warning;
23+
24+
public static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, Severity, isEnabledByDefault: true, description: Description);
25+
26+
public static bool RegisterDiagnostic(CompilationAnalysisContext context, SyntaxNode method)
27+
{
28+
var diagnosedIssue = false;
29+
30+
if (!SyntaxNodeUtils.IsInStaticClass(method))
31+
{
32+
if(TryGetInjectedVariables(method, out List<SyntaxNode> injectedVariables))
33+
{
34+
var methodVariablesUsed = method.DescendantNodes().Where(x => x.IsKind(SyntaxKind.IdentifierName));
35+
36+
var varaiblesToDiagnose = methodVariablesUsed.Where(x => injectedVariables.Exists(y => y.ToString() == x.ToString()));
37+
38+
foreach (var variable in varaiblesToDiagnose)
39+
{
40+
var diagnostic = Diagnostic.Create(Rule, variable.GetLocation(), variable);
41+
42+
if (context.Compilation.ContainsSyntaxTree(method.SyntaxTree))
43+
{
44+
context.ReportDiagnostic(diagnostic);
45+
}
46+
47+
diagnosedIssue = true;
48+
}
49+
}
50+
}
51+
52+
return diagnosedIssue;
53+
}
54+
55+
private static bool TryGetInjectedVariables(SyntaxNode method, out List<SyntaxNode> injectedVariables)
56+
{
57+
injectedVariables = new List<SyntaxNode>();
58+
var addedVariable = false;
59+
if (SyntaxNodeUtils.TryGetConstructor(method, out ConstructorDeclarationSyntax constructor))
60+
{
61+
var parameters = constructor.ParameterList.ChildNodes();
62+
var injectedParameterNames = new List<SyntaxToken>();
63+
foreach (SyntaxNode parameter in parameters)
64+
{
65+
injectedParameterNames.Add(parameter.ChildTokens().FirstOrDefault(x => x.IsKind(SyntaxKind.IdentifierToken)));
66+
}
67+
68+
var assignementExpressions = constructor.DescendantNodes().Where(x => x.IsKind(SyntaxKind.SimpleAssignmentExpression));
69+
70+
foreach (AssignmentExpressionSyntax assignmentExpression in assignementExpressions)
71+
{
72+
var injectedRightSideNode = assignmentExpression.Right.DescendantNodes().Where(x => x.IsKind(SyntaxKind.IdentifierName) && injectedParameterNames.Contains(((IdentifierNameSyntax)x).Identifier));
73+
74+
if (injectedRightSideNode == null)
75+
{
76+
continue;
77+
}
78+
79+
var assignedLeftSideNode = assignmentExpression.Left.DescendantNodes().FirstOrDefault(x => x.IsKind(SyntaxKind.IdentifierName));
80+
81+
if (assignedLeftSideNode != null)
82+
{
83+
injectedVariables.Add(assignedLeftSideNode);
84+
addedVariable = true;
85+
}
86+
}
87+
}
88+
89+
return addedVariable;
90+
}
91+
}
92+
}

src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/DeterministicMethodAnalyzer.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
3131
TimerAnalyzer.V2Rule,
3232
CancellationTokenAnalyzer.Rule,
3333
BindingAnalyzer.Rule,
34-
MethodInvocationAnalyzer.Rule);
34+
MethodInvocationAnalyzer.Rule,
35+
DependencyInjectionAnalyzer.Rule);
3536
}
3637
}
3738

@@ -65,7 +66,8 @@ private void RegisterAnalyzers(CompilationAnalysisContext context)
6566
| ThreadTaskAnalyzer.RegisterDiagnostic(context, semanticModel, methodDeclaration)
6667
| TimerAnalyzer.RegisterDiagnostic(context, semanticModel, methodDeclaration)
6768
| CancellationTokenAnalyzer.RegisterDiagnostic(context, methodDeclaration)
68-
| BindingAnalyzer.RegisterDiagnostic(context, methodDeclaration))
69+
| BindingAnalyzer.RegisterDiagnostic(context, methodDeclaration)
70+
| DependencyInjectionAnalyzer.RegisterDiagnostic(context, methodDeclaration))
6971
{
7072
methodInvocationAnalyzer.RegisterDiagnostics(context, methodInformation);
7173
}

src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/ThreadTaskAnalyzer.cs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,28 +163,22 @@ private static bool AnalyzeIdentifierTaskContinueWith(SyntaxNode method, Compila
163163

164164
private static bool HasExecuteSynchronously(SyntaxNode node)
165165
{
166-
if(!SyntaxNodeUtils.TryGetInvocationExpression(node, out SyntaxNode invocationExpression))
166+
if(!SyntaxNodeUtils.TryGetInvocationExpression(node, out InvocationExpressionSyntax invocationExpression))
167167
{
168168
return false;
169169
}
170170

171-
var argumentList = invocationExpression.ChildNodes().FirstOrDefault(x => x.IsKind(SyntaxKind.ArgumentList));
172-
173-
if (argumentList != null)
171+
var argumentList = invocationExpression.ArgumentList;
172+
foreach (ArgumentSyntax argument in argumentList.ChildNodes())
174173
{
175-
foreach (SyntaxNode argument in argumentList.ChildNodes())
174+
if (argument.Expression is MemberAccessExpressionSyntax expression)
176175
{
177-
var simpleMemberAccessExpression = argument.ChildNodes().FirstOrDefault(x => x.IsKind(SyntaxKind.SimpleMemberAccessExpression));
178-
179-
if (simpleMemberAccessExpression != null)
176+
var identifierNames = expression.ChildNodes().Where(x => x.IsKind(SyntaxKind.IdentifierName));
177+
foreach (SyntaxNode identifier in identifierNames)
180178
{
181-
var identifierNames = simpleMemberAccessExpression.ChildNodes().Where(x => x.IsKind(SyntaxKind.IdentifierName));
182-
foreach (SyntaxNode identifier in identifierNames)
179+
if (identifier.ToString().Equals("ExecuteSynchronously"))
183180
{
184-
if (identifier.ToString().Equals("ExecuteSynchronously"))
185-
{
186-
return true;
187-
}
181+
return true;
188182
}
189183
}
190184
}

src/WebJobs.Extensions.DurableTask.Analyzers/CodeFixProviderUtils.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Microsoft.CodeAnalysis;
55
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
67
using System.Linq;
78
using System.Threading;
89
using System.Threading.Tasks;
@@ -24,9 +25,9 @@ public static async Task<Document> ReplaceWithIdentifierAsync(Document document,
2425

2526
public static bool TryGetDurableOrchestrationContextVariableName(SyntaxNode node, out string variableName)
2627
{
27-
if (SyntaxNodeUtils.TryGetMethodDeclaration(node, out SyntaxNode methodDeclaration))
28+
if (SyntaxNodeUtils.TryGetMethodDeclaration(node, out MethodDeclarationSyntax methodDeclaration))
2829
{
29-
var parameterList = methodDeclaration.ChildNodes().First(x => x.IsKind(SyntaxKind.ParameterList));
30+
var parameterList = methodDeclaration.ParameterList;
3031

3132
foreach (SyntaxNode parameter in parameterList.ChildNodes())
3233
{

src/WebJobs.Extensions.DurableTask.Analyzers/CodefixProviders/Entity/StaticFunctionCodeFixProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
4646
private async Task<Document> AddStaticModifierAsync(Document document, SyntaxNode identifierNode, CancellationToken cancellationToken)
4747
{
4848
var root = await document.GetSyntaxRootAsync(cancellationToken);
49-
if (SyntaxNodeUtils.TryGetMethodDeclaration(identifierNode, out SyntaxNode methodDeclaration))
49+
if (SyntaxNodeUtils.TryGetMethodDeclaration(identifierNode, out MethodDeclarationSyntax methodDeclaration))
5050
{
51-
var newMethodDeclaration = ((MethodDeclarationSyntax)methodDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
51+
var newMethodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
5252
var newRoot = root.ReplaceNode(methodDeclaration, newMethodDeclaration);
5353

5454
return document.WithSyntaxRoot(newRoot);

src/WebJobs.Extensions.DurableTask.Analyzers/Resources.Designer.cs

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

src/WebJobs.Extensions.DurableTask.Analyzers/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@
174174
<data name="DateTimeAnalyzerTitle" xml:space="preserve">
175175
<value>DateTime calls must be deterministic inside an orchestrator function.</value>
176176
</data>
177+
<data name="DependencyInjectionAnalyzerTitle" xml:space="preserve">
178+
<value>Injected dependencies cannot be used inside an orchestrator function.</value>
179+
</data>
177180
<data name="DeterministicAnalyzerDescription" xml:space="preserve">
178181
<value>An orchestrator function must be deterministic. For more information on orchestrator code constraints, see:
179182
https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-checkpointing-and-replay#orchestrator-code-constraints</value>

0 commit comments

Comments
 (0)