Roslyn CodeFixProvider add attribute to the method - c#

I'm building CodeFixProvider for the analyzer that is detecting if custom attribute is missing from the method declaration. Basically custom attribute that should be added to the method looks like
[CustomAttribute(param1: false, param2: new int[]{1,2,3})]
this is what I've got so far:
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 declaration = root.FindToken( diagnosticSpan.Start ).Parent.AncestorsAndSelf( ).OfType<MethodDeclarationSyntax>( ).First( );
context.RegisterCodeFix(
CodeAction.Create(
title: title,
createChangedSolution: c => this.AddCustomAttribute(context.Document, declaration, c),
equivalenceKey: title),
diagnostic);
}
private async Task<Solution> AddCustomAttribute( Document document, MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken ) {
// I suspect I need to do something like methodDeclaration.AddAttributeLists(new AttributeListSyntax[] {
// but not sure how to use it exactly
throw new NotImplementedException( );
}

Remember, roslyn syntax trees are immutable. You'll need something like:
private async Task<Solution> AddCustomAttribute(Document document, MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken);
var attributes = methodDeclaration.AttributeLists.Add(
SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(
SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("CustomAttribute"))
// .WithArgumentList(...)
)).NormalizeWhitespace());
return document.WithSyntaxRoot(
root.ReplaceNode(
methodDeclaration,
methodDeclaration.WithAttributeLists(attributes)
)).Project.Solution;
}
To get the full SyntaxFactory code for the attribute constructor .WithArgumentList() throw it into the Roslyn Quoter.

Related

Cannot convert Dynamic table entity to System.threading entity in assert

I am trying to implement an Assert over my function which is called in separate project. The function called is as follows:
public async Task<List<DynamicTableEntity>> PopulateTable(
string documentId,
IEnumerable<string> lines,
JsonSchema schema,
string type,
string subType,
string format,
bool upsert ){var tableEntries = new List<DynamicTableEntity>( tableData.Count );
.....
return tableEntries; }
In Some cases it will throw an exception and I would like to test that using Xunit framework. My TDD code is like this:
public async Task UploadAndSchemaCheckOnMissingKey()
{
var table = await _tableStore.PopulateTable(null, lines, schema, "tableOutput", null, "test", false);
await Assert.ThrowsAsync<ArgumentException>(table);
}
I get the error as cannot convert System.Collection.generic.List<table.DynamicEntity> to System.Func<System.Threading.tasks.task>.
How should I handle this that My test case pass when the exception is thrown?
You can use it something like this:
Func<Task> table = async() => await _tableStore.PopulateTable(null, lines, schema, "tableOutput", null, "test", false);
var ex = await Assert.ThrowsAsync<FormatException>(table);
Assert.Contains("expected error message", ex.Message);

Roslyn CodeFixProvider add attribute with argument having value

I'm creating a CodeFixProvider for the analyzer that is detecting if MessagePackObject attribute is missing from the class declaration. Beside, My attribute need to have one argument keyAsPropertyName with value true
[MessagePackObject(keyAsPropertyName:true)]
I have done adding attribute without arguments like so(my solution method)
private async Task<Solution> AddAttributeAsync(Document document, ClassDeclarationSyntax classDecl, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken);
var attributes = classDecl.AttributeLists.Add(
SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("MessagePackObject"))
// .WithArgumentList(SyntaxFactory.AttributeArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.AttributeArgument(SyntaxFactory.("keyAsPropertyName")))))))
// .WithArgumentList(...)
)).NormalizeWhitespace());
return document.WithSyntaxRoot(
root.ReplaceNode(
classDecl,
classDecl.WithAttributeLists(attributes)
)).Project.Solution;
}
But I don't know how add attribute with argument having value. Can somebody help me please?
[MessagePackObject(keyAsPropertyName:true)] is a AttributeArgumentSyntax that has NameColons and doesn't have NameEquals, so you just need to create it passing nothing as NameEquals and passing the correct initial expression like this:
...
var attributeArgument = SyntaxFactory.AttributeArgument(
null, SyntaxFactory.NameColon("keyAsPropertyName"), SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression));
var attributes = classDecl.AttributeLists.Add(
SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("MessagePackObject"))
.WithArgumentList(SyntaxFactory.AttributeArgumentList(SyntaxFactory.SingletonSeparatedList(attributeArgument)))
)).NormalizeWhitespace());
...

Roslyn Code Action: How to check if preview or real execution?

I am currently experimenting with Roslyn and Code Actions, more specific Code Refactorings.
It feels kind of easy, but I have a difficulty I cannot solve.
Code actions are executed once against a dummy workspace as a "preview" option, so that you can see the actual changes before you click the action and execute it against the real workspace.
Now I am dealing with some things Roslyn can't really do (yet), so I am doing some changes via EnvDTE. I know, it's bad, but I couldn't find another way.
So the issue here is:
When I hover over my code action, the code gets executed as preview, and it should NOT do the EnvDTE changes. Those should only be done when the real execute happens.
I have created a gist with a small example of my code. It doesn't really makes sense, but should show what I want to achieve. Do some modifications via roslyn, then do something via EnvDTE, like changing Cursor position. But of course only on the real execution.
The relevant part for those who can't click the gist:
public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(continueOnCapturedContext: false);
var node = root.FindNode(context.Span);
var dec = node as MethodDeclarationSyntax;
if (dec == null)
return;
context.RegisterRefactoring(CodeAction.Create("MyAction", c => DoMyAction(context.Document, dec, c)));
}
private static async Task<Solution> DoMyAction(Document document, MethodDeclarationSyntax method, CancellationToken cancellationToken)
{
var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken);
var root = await syntaxTree.GetRootAsync(cancellationToken);
// some - for the question irrelevant - roslyn changes, like:
document = document.WithSyntaxRoot(root.ReplaceNode(method, method.WithIdentifier(SyntaxFactory.ParseToken(method.Identifier.Text + "Suffix"))));
// now the DTE magic
var preview = false; // <--- TODO: How to check if I am in preview here?
if (!preview)
{
var requestedItem = DTE.Solution.FindProjectItem(document.FilePath);
var window = requestedItem.Open(Constants.vsViewKindCode);
window.Activate();
var position = method.Identifier.GetLocation().GetLineSpan().EndLinePosition;
var textSelection = (TextSelection) window.Document.Selection;
textSelection.MoveTo(position.Line, position.Character);
}
return document.Project.Solution;
}
You can choose to override ComputePreviewOperationsAsync to have different behavior for Previews from regular code.
I've found the solution to my problem by digging deeper and trial and error after Keven Pilch's answer. He bumped me in the right direction.
The solution was to override both the ComputePreviewOperationsAsync and the GetChangedSolutionAsync methods in my own CodeAction.
Here the relevant part of my CustomCodeAction, or full gist here.
private readonly Func<CancellationToken, bool, Task<Solution>> _createChangedSolution;
protected override async Task<IEnumerable<CodeActionOperation>> ComputePreviewOperationsAsync(CancellationToken cancellationToken)
{
const bool isPreview = true;
// Content copied from http://sourceroslyn.io/#Microsoft.CodeAnalysis.Workspaces/CodeActions/CodeAction.cs,81b0a0866b894b0e,references
var changedSolution = await GetChangedSolutionWithPreviewAsync(cancellationToken, isPreview).ConfigureAwait(false);
if (changedSolution == null)
return null;
return new CodeActionOperation[] { new ApplyChangesOperation(changedSolution) };
}
protected override Task<Solution> GetChangedSolutionAsync(CancellationToken cancellationToken)
{
const bool isPreview = false;
return GetChangedSolutionWithPreviewAsync(cancellationToken, isPreview);
}
protected virtual Task<Solution> GetChangedSolutionWithPreviewAsync(CancellationToken cancellationToken, bool isPreview)
{
return _createChangedSolution(cancellationToken, isPreview);
}
The code to create the action stays quite similar, except the bool is added and I can check against it then:
public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
// [...]
context.RegisterRefactoring(CustomCodeAction.Create("MyAction",
(c, isPreview) => DoMyAction(context.Document, dec, c, isPreview)));
}
private static async Task<Solution> DoMyAction(Document document, MethodDeclarationSyntax method, CancellationToken cancellationToken, bool isPreview)
{
// some - for the question irrelevant - roslyn changes, like:
// [...]
// now the DTE magic
if (!isPreview)
{
// [...]
}
return document.Project.Solution;
}
Why those two?
The ComputePreviewOperationsAsync calls the the normal ComputeOperationsAsync, which internally calls ComputeOperationsAsync. This computation executes GetChangedSolutionAsync. So both ways - preview and not - end up at GetChangedSolutionAsync. That's what I actually want, calling the same code, getting a very similar solution, but giving a bool flag if it is preview or not too.
So I've written my own GetChangedSolutionWithPreviewAsync which I use instead. I have overriden the default GetChangedSolutionAsync using my custom Get function, and then ComputePreviewOperationsAsync with a fully customized body. Instead of calling ComputeOperationsAsync, which the default one does, I've copied the code of that function, and modified it to use my GetChangedSolutionWithPreviewAsync instead.
Sounds rather complicated in written from, but I guess the code above should explain it quite well.
Hope this helps other people.

Roslyn recommends incorrect symbols for a method

Just came across that in VS2015 community(as well as VS2017 RC)
public class Class1
{
public static void Bar() { }
public static void Foo(int n)
{
Bar.
}
}
After Bar. the following auto-completions are recommended.
(parameter) int n
MemberwiseClone
Equals
GetHashCode
GetType
ToString
I think the latter 4 methods (from object) makes sense because I can add a variable/field named Bar later, but I don't see (parameter) int n and MemberwiseClone make any sense. By the way, Rider has the correct behavior here. To make sure it's not a IDE specific issue, I try to get completions from Roslyn via
namespace Microsoft.CodeAnalysis.Recommendations
{
public static class Recommender
{
public static Task<IEnumerable<ISymbol>> GetRecommendedSymbolsAtPositionAsync(
SemanticModel semanticModel,
int position,
Workspace workspace,
OptionSet options = null,
CancellationToken cancellationToken = default(CancellationToken));
}
}
My first attempt
var code = #"
namespace IntellisenseTest
{
public class Class1
{
public static void Bar() { }
public static void Foo(int n)
{
Bar.
}
}
}";
var intellisense = "Bar.";
var sourceText = SourceText.From(code);
var syntaxTree = CSharpSyntaxTree.ParseText(sourceText);
var compilation = CSharpCompilation.Create("IntellisenseTest").AddSyntaxTrees(syntaxTree);
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var position = code.IndexOf(intellisense) + intellisense.Length + 1;
var workspace = MSBuildWorkspace.Create();
var symbols = await Recommender.GetRecommendedSymbolsAtPositionAsync(
semanticModel,
position,
workspace);
//print symbols
But I only get one recommended completion named n.
Then I create a real solution/project with the string code above on my hard drive.
Second attempt
var solutionPath = #"C:\workspace\IntellisenseTest\IntellisenseTest.sln";
var fileName = "Class1.cs";
var intellisense = "Bar.";
var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(solutionPath);
var project = solution.Projects.First();
var document = project.Documents.Single(d => d.Name == fileName);
var text = await document.GetTextAsync();
var position = text.ToString().IndexOf(intellisense) + intellisense.Length + 1;
var syntaxTree = await document.GetSyntaxTreeAsync();
var compilation = await project.GetCompilationAsync();
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var symbols = await Recommender.GetRecommendedSymbolsAtPositionAsync(
semanticModel,
position,
workspace);
//print symbols I got
n
ToString
Equals
GetHashCode
GetType
MemberwiseClone
Q1: What's the difference between the two implementations above? IMO I should get the same result.
Q2: Why Roslyn recommends these completions? IMO n and MemberwiseClone don't make any sense.
A2: All of the suggested completions are System.Object methods, from which all types in C# derive from.
A1: You don't have a reference to the assembly that defines System.Object in your manually constructed msbuild workspace. The one with the .sln probably has it.
In your first attempt, add it to the project
project = project.AddMetadataReference(
MetadataReference.CreateFromFile(typeof(System.Object).Assembly.Location));
or the compilation
compilation = compilation.AddReferences(
MetadataReference.CreateFromFile(typeof(System.Object).Assembly.Location));
and you should get identical results.

Create and pass a action/delegate of a method at runtime to TaskFactory.StartNew method

I need to create and pass a action/delegate of a method at runtime to TaskFactory.Satrtnew method. In general I would do the following when i know the method to use.
public void SomeService(CancellationToken cToken)
{ }
var tasks = new Task[] {
Task.Factory.StartNew(() => SomeService(new CancellationToken())
};
In my case, when I to do this at runtime, where my method names comes from configuration file. The method exists but it would be assigned to task at runtime. I tried something like
var conn = bll.daService.DbConnection;
var currentType = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType;
var method = currentType.GetMethod("SomeService");
Action<CancellationToken> action = (Action<CancellationToken>)
Delegate.CreateDelegate(typeof(Action<CancellationToken>),currentType,method);
Task.Factory.StartNew(action);
And also something like.
delegate void ServiceDelegate(CancellationToken cToken);
ServiceDelegate serDel = (ServiceDelegate)
Delegate.CreateDelegate(currentType,method);
Action ac = serDel(new CancellationToken());
Task.Factory.StartNew(ac);
None of them are working.It throws different kind of errors and exceptions.
EDIT: The following line throws exception "Type must derive from Delegate"
ServiceDelegate serDel = (ServiceDelegate)
Delegate.CreateDelegate(currentType,method)

Categories