Analyzer for MSAL.NET
Analyzer logic here
MSAL.Analyzer/MSAL.Analyzer/MSAL.Analyzer/MSAL.Analyzer/MSALAnalyzerAnalyzer.cs
Lines 29 to 126 in fb67922
public override void Initialize(AnalysisContext context) | |
{ | |
/*Issues | |
How do I get the name and location of the variable in the InvocationExpressionSyntax/MemberAccessExpressionSyntax? | |
*/ | |
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | |
context.EnableConcurrentExecution(); | |
context.RegisterCompilationStartAction((compilationStartContext) => | |
{ | |
compilationStartContext.RegisterCodeBlockStartAction<SyntaxKind>(codeBlockContext => | |
{ | |
// We only care about method bodies. | |
if (codeBlockContext.OwningSymbol.Kind != SymbolKind.Method) return; | |
var method = (IMethodSymbol)codeBlockContext.OwningSymbol; | |
var CreateMethod = "Create"; | |
var SetBeforeAccessMethod = "SetBeforeAccess"; | |
bool CreateMethodInvocationFound = false; | |
bool setBeforeOrAfterAccessInvocationFound = false; | |
MemberAccessExpressionSyntax targetInvocation = null; | |
codeBlockContext.RegisterSyntaxNodeAction(syntaxNodeContext => | |
{ | |
var BuilderType = compilationStartContext?.Compilation?.GetTypeByMetadataName("Microsoft.Identity.Client.ConfidentialClientApplicationBuilder"); | |
var TokenCacheType = compilationStartContext?.Compilation?.GetTypeByMetadataName("Microsoft.Identity.Client.ITokenCache"); | |
//var declaration = syntaxNodeContext.Node as VariableDeclaratorSyntax; | |
//var x = declaration.DescendantNodes().Where(childNode => (childNode as MemberAccessExpressionSyntax).Name?.Identifier.ValueText == CreateMethod); | |
var node = syntaxNodeContext.Node as InvocationExpressionSyntax; | |
if (node == null) | |
{ | |
return; | |
} | |
var expression = node.Expression as MemberAccessExpressionSyntax; | |
if (expression == null) | |
{ | |
return; | |
} | |
var methodName = expression.Name?.Identifier.ValueText; | |
if (CreateMethod.Equals(methodName)) | |
{ | |
var nameSyntax = expression.Expression as IdentifierNameSyntax; | |
if (nameSyntax == null) | |
{ | |
return; | |
} | |
var typeInfo = codeBlockContext?.SemanticModel?.GetTypeInfo(nameSyntax).Type as INamedTypeSymbol; | |
if (typeInfo?.ConstructedFrom == null) | |
{ | |
return; | |
} | |
if (typeInfo.ConstructedFrom.Equals(BuilderType)) | |
{ | |
CreateMethodInvocationFound = true; | |
targetInvocation = expression; | |
return; | |
} | |
} | |
if (SetBeforeAccessMethod.Equals(methodName)) | |
{ | |
var tokenCacheName = expression.DescendantNodes().Where(dNode =>dNode is IdentifierNameSyntax) | |
.Where(dNode => (dNode as IdentifierNameSyntax).Identifier.Text.Equals("UserTokenCache")).FirstOrDefault(); | |
if (tokenCacheName != null) | |
{ | |
//var type1 = codeBlockContext.SemanticModel.GetTypeInfo(tokenCacheName).Type as INamedTypeSymbol; | |
setBeforeOrAfterAccessInvocationFound = true; | |
} | |
} | |
}, SyntaxKind.InvocationExpression); | |
codeBlockContext.RegisterCodeBlockEndAction(ctx => | |
{ | |
if (CreateMethodInvocationFound && !setBeforeOrAfterAccessInvocationFound) | |
{ | |
ctx.ReportDiagnostic(Diagnostic.Create(Rule, targetInvocation.GetLocation())); | |
} | |
CreateMethodInvocationFound = false; | |
setBeforeOrAfterAccessInvocationFound = false; | |
return; | |
}); | |
}); | |
}); | |
} |
When running analyzer, MSAL.Analyzer.Vsix must be start up project.
Once new VS instance loads, open Msal.Analyzer.Test to see analyszer running live
.gitignore not configured yet
This analyzer checks for the use of ConfidentialClientApplicationBuilder.Create() reports when it sees that SetBeforeAccess is not used. This is just a POC to see if this will work.
Issues Why does the logic not find the Build() invocation but does find the Create() Why does the analyzer logic loop twice? How do I get the name and location of the variable in the InvocationExpressionSyntax/MemberAccessExpressionSyntax? Why does the analyzer not detect the usage of SetBeforeAccess?