How do I debug a CodeFixProvider not ever loading - c#

I have made a new "Analyzer with Code Fix (.NET Standard)" project, and updated the analyzer to check for what I want, and that works great. Now I have modified the CodeFixProvider, but it never shows up when debugging.
I have set breakpoints in the getter for FixableDiagnosticIds, GetFixAllProvider(), and RegisterCodeFixesAsync(CodeFixContext context), yet none of the breakpoints ever get called when I click the "light bulb" on a line that the analyzer, properly, tags.
Any ideas on how to figure out why it doesn't seem to be getting called?
In the default project created by "Analyzer with Code Fix (.NET Standard)", breakpoints in those 3 places are getting called correctly.
My analyzer code
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace InAnalyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class InAnalyzerAnalyzer : DiagnosticAnalyzer
{
public const string CanBeInvokedWithInDiagnosticId = "IN3001";
public const string DoNotUseInWithParameterDiagnosticId = "IN3002";
public const string UseInWithParameterDiagnosticId = "IN3003";
private const string CanBeInvokedWithInCategory = "Performance";
private const string DoNotUseInWithParameterCategory = "Performance";
private const string UseInWithParameterCategory = "Performance";
private static readonly LocalizableString CanBeInvokedWithInTitle = new LocalizableResourceString(nameof(Resources.CanBeInvokedWithInAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString DoNotUseInWithParameterTitle = new LocalizableResourceString( nameof(Resources.DoNotUseInWithParameterAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString UseInWithParameterTitle = new LocalizableResourceString( nameof(Resources.UseInWithParameterAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString CanBeInvokedWithInMessageFormat = new LocalizableResourceString(nameof(Resources.CanBeInvokedWithInAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString DoNotUseInWithParameterMessageFormat = new LocalizableResourceString(nameof(Resources.DoNotUseInWithParameterAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString UseInWithParameterMessageFormat = new LocalizableResourceString(nameof(Resources.UseInWithParameterAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString CanBeInvokedWithInDescription = new LocalizableResourceString(nameof(Resources.CanBeInvokedWithInAnalyzerDescription), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString DoNotUseInWithParameterDescription = new LocalizableResourceString(nameof(Resources.DoNotUseInWithParameterAnalyzerDescription), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString UseInWithParameterDescription = new LocalizableResourceString(nameof(Resources.UseInWithParameterAnalyzerDescription), Resources.ResourceManager, typeof(Resources));
private static readonly DiagnosticDescriptor CanBeInvokedWithInRule = new DiagnosticDescriptor(
CanBeInvokedWithInDiagnosticId,
CanBeInvokedWithInTitle,
CanBeInvokedWithInMessageFormat,
CanBeInvokedWithInCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
CanBeInvokedWithInDescription);
private static readonly DiagnosticDescriptor DoNotUseInWithParameterRule = new DiagnosticDescriptor(
DoNotUseInWithParameterDiagnosticId,
DoNotUseInWithParameterTitle,
DoNotUseInWithParameterMessageFormat,
DoNotUseInWithParameterCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
DoNotUseInWithParameterDescription);
private static readonly DiagnosticDescriptor UseInWithParameterRule = new DiagnosticDescriptor(
UseInWithParameterDiagnosticId,
UseInWithParameterTitle,
UseInWithParameterMessageFormat,
UseInWithParameterCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
UseInWithParameterDescription);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(CanBeInvokedWithInRule, DoNotUseInWithParameterRule, UseInWithParameterRule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeInvocationExpressionSyntaxNode, SyntaxKind.InvocationExpression);
context.RegisterSyntaxNodeAction(AnalyzeMethodDeclarationSyntaxNode, SyntaxKind.MethodDeclaration);
}
private static void AnalyzeInvocationExpressionSyntaxNode(SyntaxNodeAnalysisContext context)
{
var node = (InvocationExpressionSyntax) context.Node;
var symbol = context.SemanticModel.GetSymbolInfo(node).Symbol ??
context.SemanticModel.GetDeclaredSymbol(node);
if (symbol is IMethodSymbol methodSymbol)
{
var parametersSymbol = methodSymbol.Parameters;
var argumentSyntaxList = node?.ArgumentList.Arguments;
if (argumentSyntaxList != null)
{
var argumentSyntaxes = argumentSyntaxList.Value;
for (var index = 0; index < parametersSymbol.Length; index++)
{
var parameterSymbol = parametersSymbol[index];
if (parameterSymbol.RefKind == RefKind.In &&
parameterSymbol.Type.IsReadOnly &&
parameterSymbol.Type.IsValueType &&
index < argumentSyntaxes.Count)
{
var argumentSyntax = argumentSyntaxes[index];
if (argumentSyntax?.RefKindKeyword.IsKind(SyntaxKind.InKeyword) == false)
{
var diagnostic = Diagnostic.Create(
CanBeInvokedWithInRule,
argumentSyntax.Expression.GetLocation(),
parameterSymbol.Name,
parameterSymbol.Type);
context.ReportDiagnostic(diagnostic);
}
}
}
}
}
}
private static void AnalyzeMethodDeclarationSyntaxNode(SyntaxNodeAnalysisContext context)
{
var node = (MethodDeclarationSyntax) context.Node;
var parameterSyntaxList = node?.ParameterList.Parameters;
if (parameterSyntaxList != null)
{
var parameterSyntaxes = parameterSyntaxList.Value;
for (var index = 0; index < parameterSyntaxes.Count; index++)
{
var parameterSyntax = parameterSyntaxes[index];
if (parameterSyntax != null)
{
var symbol = context.SemanticModel.GetSymbolInfo(parameterSyntax.Type).Symbol;
if (symbol is ITypeSymbol typeSymbol)
{
if (typeSymbol.IsReadOnly &&
typeSymbol.IsValueType)
{
if (!parameterSyntax.Modifiers.Any(SyntaxKind.InKeyword))
{
var diagnostic = Diagnostic.Create(
UseInWithParameterRule,
parameterSyntax.Identifier.GetLocation(),
parameterSyntax.Identifier,
typeSymbol);
context.ReportDiagnostic(diagnostic);
}
}
else
{
foreach (var modifier in parameterSyntax.Modifiers)
{
if (modifier.Kind() == SyntaxKind.InKeyword)
{
var diagnostic = Diagnostic.Create(
DoNotUseInWithParameterRule,
modifier.GetLocation(),
parameterSyntax.Identifier,
typeSymbol);
context.ReportDiagnostic(diagnostic);
}
}
}
}
}
}
}
}
}
}
My CodeFixProvider (Probably not complete and correct; I'd like to debug and get it working correctly, but I can't get it to even try to run):
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
namespace InAnalyzer
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(InAnalyzerCodeFixProvider)), Shared]
public class InAnalyzerCodeFixProvider : CodeFixProvider
{
private const string AddInModifierTitle = "Add 'in' modifier";
private const string RemoveInModifierTitle = "Remove 'in' modifier";
public sealed override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(
InAnalyzerAnalyzer.CanBeInvokedWithInDiagnosticId,
InAnalyzerAnalyzer.DoNotUseInWithParameterDiagnosticId,
InAnalyzerAnalyzer.UseInWithParameterDiagnosticId);
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 token = root.FindToken(context.Span.Start);
if (!token.Span.IntersectsWith(context.Span))
{
return;
}
var generator = SyntaxGenerator.GetGenerator(context.Document);
var node = generator.GetDeclaration(token.Parent);
if (node == null)
{
return;
}
foreach (var diagnostic in context.Diagnostics)
{
switch (diagnostic.Id)
{
case InAnalyzerAnalyzer.CanBeInvokedWithInDiagnosticId:
case InAnalyzerAnalyzer.UseInWithParameterDiagnosticId:
context.RegisterCodeFix(
CodeAction.Create(
AddInModifierTitle,
c => AddInModifierAsync(context.Document, node, c),
AddInModifierTitle),
diagnostic);
break;
case InAnalyzerAnalyzer.DoNotUseInWithParameterDiagnosticId:
context.RegisterCodeFix(
CodeAction.Create(
RemoveInModifierTitle,
c => RemoveInModifierAsync(context.Document, node, c),
RemoveInModifierTitle),
diagnostic);
break;
}
}
}
private async Task<Document> AddInModifierAsync(
Document document,
SyntaxNode node,
CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
switch (node.Parent)
{
case ArgumentSyntax argumentSyntax:
editor.ReplaceNode(
argumentSyntax,
argumentSyntax.WithRefKindKeyword(SyntaxFactory.Token(SyntaxKind.InKeyword)));
break;
case ParameterSyntax parameterSyntax:
editor.ReplaceNode(
parameterSyntax,
parameterSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.InKeyword)));
break;
}
return editor.GetChangedDocument();
}
private async Task<Document> RemoveInModifierAsync(
Document document,
SyntaxNode node,
CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
////switch (node.Parent)
////{
//// case ArgumentSyntax argumentSyntax:
//// editor.ReplaceNode(
//// argumentSyntax,
//// argumentSyntax.WithRefKindKeyword(SyntaxFactory.Token(SyntaxKind.InKeyword)));
//// break;
//// case ParameterSyntax parameterSyntax:
//// editor.ReplaceNode(
//// parameterSyntax,
//// parameterSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.InKeyword)));
//// break;
////}
return editor.GetChangedDocument();
}
}
}

After finding this, which pointed to the CreateExpInstance tool, I tried resetting the "Roslyn"-suffixed experimental instance. That didn't solve my issue, but I then deleted my "Roslyn"-suffixed experimental instance and tried debugging my VSIX again. This time, debugging my VSIX worked and showed my CodeFixProvider.
rd /s/q "%LOCALAPPDATA%\Microsoft\VisualStudio\16.0_0f71fe5bRoslyn"

For anyone else suffering this stuff, try this:
Find the Reset visual studio experimental instance short cut command by using the Start search menu
Run it
Clean your solution
Run it in RELEASE mode
Notice that your stuff now appears in the instance
Close it
Switch back to DEBUG model
Et voila. Le black magique just happened. Problem Solv-ed.

Related

Roslyn Analyzer not clearing last diagnostic

I have created a Roslyn Analyzer that validates text files against a set of rules and have noticed that when I fix the last issue that diagnostic does not disappear. I have created a simple example to replicate the problem below.
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class Analyzer1Analyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "TestCompilationAnalyzer";
private static readonly LocalizableString Title = "TestAnalyzer";
private static readonly LocalizableString MessageFormat = "This message should always appear {0}";
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, "Test", DiagnosticSeverity.Warning, isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterAdditionalFileAction(AdditionalFileAction);
}
private void AdditionalFileAction(AdditionalFileAnalysisContext cxt)
{
var text = File.ReadAllText(cxt.AdditionalFile.Path);
if (text.Contains("help"))
{
cxt.ReportDiagnostic(Diagnostic.Create(Rule, Location.None, $"help"));
}
if (text.Contains("test"))
{
cxt.ReportDiagnostic(Diagnostic.Create(Rule, Location.None, $"test"));
}
}
}
if I add the words help and test to my additional file both diagnostics appear but if I remove both whatever was showing last won't disappear.
I found a workaround using code generators instead
[Generator]
public class SchemaTestAnalyzer : ISourceGenerator
{
public const string DiagnosticId = "TestCompilationAnalyzer";
private static readonly LocalizableString Title = "TestAnalyzer";
private static readonly LocalizableString MessageFormat = "This message should always appear {0}";
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, "Test", DiagnosticSeverity.Warning, isEnabledByDefault: true);
public void Execute(GeneratorExecutionContext context)
{
var additionalFile = context.AdditionalFiles.FirstOrDefault();
if (additionalFile != null)
{
var text = File.ReadAllText(additionalFile.Path);
if (text.Contains("help"))
{
context.ReportDiagnostic(Diagnostic.Create(Rule, Location.None, "help"));
}
if (text.Contains("test"))
{
context.ReportDiagnostic(Diagnostic.Create(Rule, Location.None, "test"));
}
}
}
public void Initialize(GeneratorInitializationContext context) { }
}
these work as expected and the last issue disappears when addressed and re-analyzed

How to give testcase name to log4net log file for every separate test case

I tried implementing log4net for my automation framework.
I have not written the XML config file. I have used code file only for configuring it.
class log4netHelper
{
private static ILog _logger;
private static ConsoleAppender _conAppender;
private static FileAppender _fileAppender;
private static RollingFileAppender _rollingFileAppender;
private static string _layout = "%date{ABSOLUTE} [%class] [%level] [%method] - %message%newline";
public static string Layout
{
set { _layout = value; }
}
private static PatternLayout GetPatternLayout()
{
var patternLayout = new PatternLayout()
{
ConversionPattern = _layout
};
patternLayout.ActivateOptions();
return patternLayout;
}
private static ConsoleAppender GetConsoleAppender()
{
var consoleAppender = new ConsoleAppender()
{
Name = "ConsoleAppender",
Layout = GetPatternLayout(),
Threshold = Level.Error
};
consoleAppender.ActivateOptions();
return consoleAppender;
}
private static FileAppender GetFileAppender()
{
var fileAppender = new FileAppender()
{
Name = "FileAppender",
Layout = GetPatternLayout(),
Threshold = Level.All,
AppendToFile = false,
File = #"C:\FileLogger.log"
};
fileAppender.ActivateOptions();
return fileAppender;
}
private static RollingFileAppender GetRollingFileAppender()
{
var rollingFileAppender = new RollingFileAppender()
{
Name = "RollingFileAppender",
Layout = GetPatternLayout(),
Threshold = Level.All,
AppendToFile = true,
File = "RollingfileLogger.log",
MaximumFileSize = "1MB",
MaxSizeRollBackups = 15
};
rollingFileAppender.ActivateOptions();
return rollingFileAppender;
}
public static ILog GetLogger([CallerFilePath]string filename = "")
{
if (_conAppender == null)
_conAppender = GetConsoleAppender();
if (_fileAppender == null)
_fileAppender = GetFileAppender();
if (_rollingFileAppender == null)
_rollingFileAppender = GetRollingFileAppender();
BasicConfigurator.Configure(_conAppender, _fileAppender, _rollingFileAppender);
return LogManager.GetLogger(filename);
}
}
instead of the FileLogger.log file, I want the name of the test case for every test case run.
I am not able to come up with any solution yet on how to do this.
I tried changing the File variable value in GetAppender() but couldn't do.
Also tried passing the parameter filename in test case but still the file is created by the name of FileLogger.
This is just a concept how to use TestName as a log file name, but you will get an idea.
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Core;
using log4net.Layout;
using log4net.Repository.Hierarchy;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTest
{
public static class Log4NetHelper
{
private static readonly string _layout = "%date{ABSOLUTE} [%class] [%level] [%method] - %message%newline";
private static readonly string _appenderName = "FileAppender";
private static PatternLayout GetPatternLayout()
{
PatternLayout patternLayout = new PatternLayout
{
ConversionPattern = _layout
};
patternLayout.ActivateOptions();
return patternLayout;
}
private static FileAppender GetFileAppender(string fileName)
{
var fileAppender = new FileAppender
{
Name = _appenderName,
Layout = GetPatternLayout(),
Threshold = Level.All,
AppendToFile = false,
File = $#"C:\temp\{fileName}.log"
};
fileAppender.ActivateOptions();
return fileAppender;
}
public static ILog GetLogger(string filename)
{
// Remember to clear old logger
Logger root = ((Hierarchy)LogManager.GetRepository()).Root;
root.RemoveAppender(_appenderName);
BasicConfigurator.Configure(GetFileAppender(filename));
return LogManager.GetLogger(filename);
}
}
[TestClass]
public class MyTestClass
{
public TestContext TestContext { get; set; }
private ILog log;
[TestInitialize]
public void TestInitialize()
{
log = Log4NetHelper.GetLogger($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}");
}
[TestMethod]
public void TestMethod1()
{
log.Info("This is my log message from TestMethod1");
Assert.IsTrue(true);
}
[TestMethod]
public void TestMethod2()
{
log.Info("This is my log message from TestMethod2");
Assert.IsTrue(true);
}
}
}
This will generate 2 files in C:\logs directory:
UnitTest.MyTestClass.TestMethod1.log
UnitTest.MyTestClass.TestMethod2.log
each with one single log message.

C# CodeFixes: Minimum Requirement that codefix is displayed

I recently started with CodeAnalyzers and CodeFixes.
Of course the template coming with the sdk () works. But when I move on, the codefix does not show up anymore on debugging.
When walking through the diagnostic is created as it is supposed to be.
BUT: The codefix (Strg+.) isnt shown anymore. I took care of Diagnostic-Id, equivalence-key, ... but cannot figure out, why I dont get my codefix shown anymore. So what are the minimum "requirements" for a codefix to be shown for a diagnostic?
Here is the code of the analyzer:
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class VirtualMemberAnalyzer : DiagnosticAnalyzer
{
public const string PublicVirtualMethod_DiagnosticId = "PublicVirtualMethod";
private const string Category = "Naming";
private static readonly DiagnosticDescriptor PublicMethodVirtualRule = new DiagnosticDescriptor(
PublicVirtualMethod_DiagnosticId,
"public methode not virtual",
"every public methode must be virtual",
Category,
DiagnosticSeverity.Error,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(PublicMethodVirtualRule);
public override void Initialize(AnalysisContext context)
{
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
}
private static void AnalyzeSymbol(SymbolAnalysisContext context)
{
var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;
foreach (var methodSymbol in namedTypeSymbol.GetMembers().OfType<IMethodSymbol>())
{
if (methodSymbol.MethodKind == MethodKind.Ordinary && !methodSymbol.IsStatic && !methodSymbol.IsVirtual && methodSymbol.DeclaredAccessibility == Accessibility.Public)
{
var diagnostic = Diagnostic.Create(
PublicMethodVirtualRule,
methodSymbol.Locations[0],
methodSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
}
}
}
And of the Codefix-Provider:
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(VirtualMemberAnalyzersCodeFixProvider)), Shared]
public class VirtualMemberAnalyzersCodeFixProvider : CodeFixProvider {
private const string title_property = "Make property virtual";
private const string title_method = "Make method virtual";
public sealed override ImmutableArray<string> FixableDiagnosticIds {
get {
return ImmutableArray.Create(VirtualMemberAnalyzer.PublicVirtualMethod_DiagnosticId);
}
}
public sealed override FixAllProvider GetFixAllProvider() {
return WellKnownFixAllProviders.BatchFixer;
}
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) {
var root = await context.Document.GetSyntaxRootAsync();
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;
var methodDeclarations = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<MethodDeclarationSyntax>().FirstOrDefault();
if (methodDeclarations != null) {
CodeAction codeAction = CodeAction.Create(title_method, c => MakeVirtual(context.Document, methodDeclarations, c), equivalenceKey: title_method);
context.RegisterCodeFix(codeAction, diagnostic);
}
}
private async Task<Document> MakeVirtual(Document document, MethodDeclarationSyntax memberDeclaration, CancellationToken cancellationToken)
{
SyntaxTokenList memberDeclarationModifiers = memberDeclaration.Modifiers;
memberDeclarationModifiers.Add(SyntaxFactory.Token(SyntaxKind.VirtualKeyword));
MethodDeclarationSyntax methodDeclarationSyntax = memberDeclaration.WithModifiers(memberDeclarationModifiers);
var oldRoot = await document.GetSyntaxRootAsync(cancellationToken);
var newRoot = oldRoot.ReplaceNode(memberDeclaration, methodDeclarationSyntax);
return document.WithSyntaxRoot(newRoot);
}
}
The memberDeclarationModifiers.Add(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); returns a new SyntaxTokenList. You're not using the new created list. Remember that SyntaxTrees are Immutable in Roslyn . Every change in the tree will create a new tree. Here is your codefix for your roslyn codefix :)
private async Task<Document> MakeVirtual(Document document, MethodDeclarationSyntax memberDeclaration, CancellationToken cancellationToken)
{
var methodDeclarationSyntax =
memberDeclaration.WithModifiers(
memberDeclaration.Modifiers.Add( SyntaxFactory.Token( SyntaxKind.VirtualKeyword ) ) );
var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait( false );
var newRoot = oldRoot.ReplaceNode(memberDeclaration, methodDeclarationSyntax);
return document.WithSyntaxRoot(newRoot);
}

Create a Roslyn analyzer to detect the "Sleep()" method call in a .cs file

I am asking this question in continuity to this one
I would like to create a roslyn analyzer to detect the use of the sleep method in a .cs file. Could someone please help me correct my code ?
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace SleepCustomRule
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SleepCustomRuleAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "SleepCheck";
private const string Title = "Sleep function use in forbidden";
private const string MessageFormat = "Remove this usage of sleep function";
private const string Description = "Sleep function forbidden";
private const string Category = "Usage";
private static readonly ISet<string> Bannedfunctions = ImmutableHashSet.Create("Sleep");
private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category,
DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.GenericName);
}
private static void AnalyzeNode(SymbolAnalysisContext context)
{
var tree = CSharpSyntaxTree.ParseText(#"
public class Sample
{
public string FooProperty {get; set;}
public void FooMethod()
{
}
}");
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { tree }, references: new[] { mscorlib });
var semanticModel = compilation.GetSemanticModel(tree);
var methods = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var node in methods)
{
if (node != null)
{
// node – is your current syntax node
// semanticalModel – is your semantical model
ISymbol symbol = semanticModel.GetSymbolInfo(node).Symbol ?? semanticModel.GetDeclaredSymbol(node);
if (symbol.Kind == SymbolKind.Method)
{
string methodName = "sleep";
if ((symbol as IMethodSymbol).Name.ToLower() == methodName)
{
// you find your method
var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], symbol.Name);
context.ReportDiagnostic(Diagnostic.Create(Rule, node.Identifier.GetLocation()));
}
}
}
}
}
}
}
I have those two errors which I could not correct:
1- RegisterSyntaxNodeAction in
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.GenericName);
2- Assembly in
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
3- Besides, I would like to detect the use of the sleep method from a given file not from the var tree I made.
Thank you so much in advance!

Roslyn Vsix stopped working after moving the project

I had a working Roslyn VSIX Project for analyzing resources that could be localizable. Everything was working fine until I moved the project to a new location.
The analyzer's still seem to run and trigger the Code fix, However, the Code Action is never Registered into visual studio, so there is no option for that fix for some reason.
My analyzer:
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ConstDiagnosticAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "Viventium.Localization.Tools.ConstantToResource";
// You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.ConstAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.ConstAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.ConstAnalyzerDescription), Resources.ResourceManager, typeof(Resources));
private const string Category = "Naming";
private static ResourceLocalizationRule localizationRule = new ResourceLocalizationRule();
private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeConstDeclaration, SyntaxKind.FieldDeclaration);
}
public static void AnalyzeConstDeclaration(SyntaxNodeAnalysisContext context)
{
var fieldDeclaration = (FieldDeclarationSyntax)context.Node;
if (false == IsValidConstDeclaration(context, fieldDeclaration))
{
return;
}
var firstVariable = fieldDeclaration.Declaration.Variables.FirstOrDefault();
var firstSymbol = context.SemanticModel.GetDeclaredSymbol(firstVariable);
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), firstSymbol.Name));
}
private static bool VariableIsInResx(SyntaxNodeAnalysisContext context, FieldDeclarationSyntax fieldDeclaration)
{
var solution = context.GetSolution();
var documentPropertyMap = PropertyMapCache.GetDocumentPropertyMap(solution);
return localizationRule.IsFieldDeclaredInResx(fieldDeclaration, documentPropertyMap);
}
private static bool IsValidConstDeclaration(SyntaxNodeAnalysisContext context, FieldDeclarationSyntax fieldDeclaration)
{
if (false == fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
return false;
}
if (fieldDeclaration.Declaration.Variables.Count > 1)
{
return false;
}
var declaredVariable = fieldDeclaration.Declaration.Variables.FirstOrDefault();
var initializer = declaredVariable.Initializer;
if (initializer == null)
{
return false;
}
var constantValue = context.SemanticModel.GetConstantValue(initializer.Value);
if (!constantValue.HasValue)
{
return false;
}
var variableTypeName = fieldDeclaration.Declaration.Type;
var variableType = context.SemanticModel.GetTypeInfo(variableTypeName).ConvertedType;
if (variableType.SpecialType != SpecialType.System_String)
{
return false;
}
return true;
}
}
The Fix Provider:
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ConstantToResourceCodeFixProvider)), Shared]
public class ConstantToResourceCodeFixProvider : CodeFixProvider
{
#region Consts
private const string title = "Convert Constant Use Resources";
#endregion
#region CodeFixProvider Overrides
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(ConstDiagnosticAnalyzer.DiagnosticId); }
}
public sealed override FixAllProvider GetFixAllProvider()
{
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
return WellKnownFixAllProviders.BatchFixer;
}
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
// TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;
// Find the type declaration identified by the diagnostic.
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf<FieldDeclarationSyntax>().First();
// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeActionWithPreview.CreateStateful(
title: title,
createChangedDocument: (c, isPreview) => this.CheckConstForLocalization(context, declaration, isPreview, c),
equivalenceKey: title),
diagnostic);
}
#endregion
#region Analyzer Logic
private async Task<Document> CheckConstForLocalization(CodeFixContext context, FieldDeclarationSyntax field, bool isPreview, CancellationToken cancellationToken)
{
var documentPropertyMap = PropertyMapCache.GetDocumentPropertyMap(context.Document.Project.Solution);
var localizationRule = new ResourceLocalizationRule();
var updatedDocument = localizationRule.ConvertDocumentToUseResources(context.Document, documentPropertyMap, field, isPreview, cancellationToken);
return await updatedDocument;
}
#endregion
}
Can some one please give me a hint to of what is going on an how I can fix this?
After hours of debugging it turns out Diagnostic Analyzer's are not allowed to have periods in them...
public const string DiagnosticId = "Viventium_Localization_Tools_ConstantToResource";
The fix was quite simple I just changed the DiagnosticId to use underscores, and I've opened a bug with Roslyn

Categories

Resources