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);
}
Related
I'm new at working on the abp.io framework, precisely Angular + Entity Framework Core.
I want to be able to create or display an objects list of a class that I've created.
For example, I've made a class called Address on the Domain layer.
Here is its AppService on the Application layer:
namespace ASKOM.RefPlusStudio.core.Services
{
[Authorize(corePermissions.Addresses.Default)]
public class AddressAppService : coreAppService, IAddressAppService
{
private readonly IAddressRepository _addressRepository;
private readonly AddressManager _addressManager;
public AddressAppService(IAddressRepository addressRepository, AddressManager addressManager)
{
_addressRepository = addressRepository;
_addressManager = addressManager;
}
[Authorize(corePermissions.Addresses.Create)]
public async Task<AddressDto> CreateAsync(CreateUpdateAddressDto input)
{
var address = await _addressManager.CreateAsync(
input.StreetNumber,
input.StreetName,
input.PostalCode,
input.City,
input.Country
);
await _addressRepository.InsertAsync(address);
return ObjectMapper.Map<Address, AddressDto>(address);
}
[Authorize(corePermissions.Addresses.Delete)]
public async Task DeleteAsync(Guid id)
{
await _addressRepository.DeleteAsync(id);
}
public async Task<AddressDto> GetAsync(Guid id)
{
var address = await _addressRepository.GetAsync(id);
return ObjectMapper.Map<Address, AddressDto>(address);
}
public async Task<PagedResultDto<AddressDto>> GetListAsync(GetAddressListDto input)
{
if (input.Sorting.IsNullOrWhiteSpace())
{
input.Sorting = nameof(Address.Country);
}
var addresses = await _addressRepository.GetListAsync(
input.SkipCount,
input.MaxResultCount,
input.Sorting,
input.Filter
);
var totalCount = await AsyncExecuter.CountAsync(
_addressRepository.WhereIf(
!input.Filter.IsNullOrWhiteSpace(),
address => address.Country.Contains(input.Filter)
)
);
return new PagedResultDto<AddressDto>(
totalCount,
ObjectMapper.Map<List<Address>, List<AddressDto>>(addresses)
);
}
[Authorize(corePermissions.Addresses.Edit)]
public async Task UpdateAsync(Guid id, CreateUpdateAddressDto input)
{
var address = await _addressRepository.GetAsync(id);
address.StreetNumber = input.StreetNumber;
address.StreetName = input.StreetName;
address.PostalCode = input.PostalCode;
address.City = input.City;
address.Country = input.Country;
await _addressRepository.UpdateAsync(address);
}
}
}
I gave them all the permissions needed.
Here is corePermissions.cs :
namespace ASKOM.RefPlusStudio.core.Permissions
{
public static class corePermissions
{
public const string GroupName = "core";
//Add your own permission names. Example:
//public const string MyPermission1 = GroupName + ".MyPermission1";
public static class Addresses
{
public const string Default = GroupName + ".Addresses";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
}
}
}
I wanted to see if it can display an Addresses list so I've made a static one on DataSeeder:
namespace ASKOM.RefPlusStudio.core
{
public class coreDataSeederContributor : IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Address, Guid> _addressRepository;
private readonly IGuidGenerator _guidGenerator;
public coreDataSeederContributor(IRepository<Address, Guid> addressRepository, IGuidGenerator guidGenerator)
{
_addressRepository = addressRepository;
_guidGenerator = guidGenerator;
}
public async Task SeedAsync(DataSeedContext context)
{
if (await _addressRepository.GetCountAsync() > 0)
{
return;
}
var address = new Address(
id: _guidGenerator.Create(),
streetNumber: 07,
streetName: "Med Salah Belhaj",
postalCode: 2080,
city: "Ariana",
country: "Tunisie"
);
//autoSave: true
await _addressRepository.InsertAsync(address);
}
}
}
Here is now the result on Swagger UI:
When I try to open the request URL, here is what it shows:
I'm sure that I may have forgotten something and that's why I get Access Denied on the Request URL, but I don't really know what it is because I'm new at this.
Could you please help me?
Thank you
There was a problem with the database. That's why it hasn't read the data I've provided in the DataSeedProvider.
I want to Test the Method FindAsync(Expression<Func>) from the MongoDB C# Driver. My goal is it to
test my expression, I don't want to Mock the FindAsync Method itself. In EF I would Mock the DB Set and return my own Enumerator which is filled with my IEnumerable with test data. Can I do something like that in MongoDB also?
I using the Driver Version 2.10.4
And the Framework Moq 4.14.5
The problem you have here is FindAsync(Expression<Func>) is an extention method that just creates a new ExpressionFilterDefinition with your expression and passes it on.
Expression<Func<TDocument, bool>> filter = // some expression.
collection.FindAsync(new ExpressionFilterDefinition<TDocument>(filter), options, cancellationToken);
So you'll need to mock out the following method on IMongoCollection<TDocument>
public override Task<IAsyncCursor<TProjection>> FindAsync<TProjection>(
FilterDefinition<TDocument> filter,
FindOptions<TDocument, TProjection> options,
CancellationToken cancellationToken = default (CancellationToken));
Here's a quick example using Moq
var list = new List<Class>
{
new Class {Id = "1", Name = "Name1"},
new Class {Id = "2", Name = "Name2"},
new Class {Id = "3", Name = "Name3"}
};
var collection = new Mock<IMongoCollection<Class>>();
collection.Setup(x => x.FindAsync<Class>(
It.IsAny<FilterDefinition<Class>>(),
It.IsAny<FindOptions<Class, Class>>(),
It.IsAny<CancellationToken>()
)).ReturnsAsync((FilterDefinition<Class> filter,
FindOptions<Class, Class> options,
CancellationToken cancellationToken) =>
{
// We'll need to get back the expression
var expressionFilter = (ExpressionFilterDefinition<Class>)filter;
// Filtered our mocked list
var filtererList = list.Where(expressionFilter.Expression.Compile())
.ToList();
// Return a stubbed cursor with our filtered list
return new StubAsyncCursor<Class>(filtererList) as IAsyncCursor<Class>;
});
var cursor = await collection.Object.FindAsync(x => x.Id == "2");
var result = await cursor.ToListAsync();
foreach (var item in result)
{
Console.WriteLine($#"Id: {item.Id}, Name: {item.Name}");
}
// Output
// Id: 2, Name: Name2
public class StubAsyncCursor<T> : IAsyncCursor<T>
{
private bool _disposed;
private bool _moved;
private readonly ICollection<T> _current;
public StubAsyncCursor(ICollection<T> current)
{
this._current = current;
}
public IEnumerable<T> Current
{
get
{
this.ThrowIfDisposed();
if (!this._moved)
throw new InvalidOperationException("Must call MoveNextAsync first");
return this._current;
}
}
public bool MoveNext(CancellationToken cancellationToken)
{
this.ThrowIfDisposed();
if (this._moved)
return false;
this._moved = true;
return true;
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
this.ThrowIfDisposed();
return Task.FromResult<bool>(this.MoveNext(cancellationToken));
}
public void Dispose()
{
this._disposed = true;
}
private void ThrowIfDisposed()
{
if (this._disposed)
throw new ObjectDisposedException(this.GetType().Name);
}
}
public class Class
{
public string Id { get; set; }
public string Name { get; set; }
}
Even though this is possible, this will only work when using an expression filter definition, I'd personally recommend just spinning up a local MongoDB instance, It's fast, simple and will prove all your queries work as they do in production.
docker run -d -p 27017:27107
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.
How can I get user input before building the form. For example if the user typed "exit" at any time during the formflow, I want to save the user input into a status variable and check if it equals "exit" and if it does then return null or do some code.
namespace MyBot.Helpers
{
public enum Person
{
// [Describe("I am a Student")]
IAmStudent,
// [Describe("I am an Alumni")]
IAmAlumni,
// [Describe("Other")]
Other
};
public enum HardInfo { Yes, No };
[Serializable]
public class FeedBackClass
{
public bool AskToSpecifyOther = true;
public string OtherRequest = string.Empty;
[Prompt("May I Have Your Name?")]
[Pattern(#"^[a-zA-Z ]*$")]
public string Name { get; set; }
[Prompt("What is your Email Address?")]
public string Email { get; set; }
[Prompt("Please Select From The Following? {||}")]
[Template(TemplateUsage.NotUnderstood, "What does \"{0}\" mean?", ChoiceStyle = ChoiceStyleOptions.Auto)]
public Person? PersonType { get; set; }
[Prompt("Please Specify Other? {||}")]
public string OtherType { get; set; }
[Prompt("Was The Information You Are Looking For Hard To Find? {||}")]
[Template(TemplateUsage.NotUnderstood, "What does \"{0}\" mean?", ChoiceStyle = ChoiceStyleOptions.Auto)]
public HardInfo? HardToFindInfo { get; set; }
public static IForm<FeedBackClass> MYBuildForm()
{
var status = "exit";
if (status == null) {
return null;
}
else
{
return new FormBuilder<FeedBackClass>()
.Field(nameof(Name), validate: ValidateName)
.Field(nameof(Email), validate: ValidateContactInformation)
.Field(new FieldReflector<FeedBackClass>(nameof(PersonType))
.SetActive(state => state.AskToSpecifyOther)
.SetNext(SetNext))
.Field(nameof(OtherType), state => state.OtherRequest.Contains("oth"))
.Field(nameof(HardToFindInfo)).Confirm("Is this your selection?\n{*}")
.OnCompletion(async (context, state) =>
{
await context.PostAsync("Thanks for your feedback! You are Awsome!");
context.Done<object>(new object());
})
.Build();
}
if the user typed "exit" at any time during the formflow, I want to save the user input into a status variable and check if it equals "exit" and if it does then return null or do some code.
It seems that you’d like to implement global handler to process "exit" command. Scorables can intercept every message sent to a Conversation and apply a score to the message based on logic you define, which can help you achieve it, you can try it.
For detailed information, please refer to Global message handlers using scorables or this Global Message Handlers Sample
The following code snippet work for me, you can refer to it.
ExitDialog:
public class ExitDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("This is the Settings Dialog. Reply with anything to return to prior dialog.");
context.Wait(this.MessageReceived);
}
private async Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
if ((message.Text != null) && (message.Text.Trim().Length > 0))
{
context.Done<object>(null);
}
else
{
context.Fail(new Exception("Message was not a string or was an empty string."));
}
}
}
ExitScorable:
public class ExitScorable : ScorableBase<IActivity, string, double>
{
private readonly IDialogTask task;
public ExitScorable(IDialogTask task)
{
SetField.NotNull(out this.task, nameof(task), task);
}
protected override async Task<string> PrepareAsync(IActivity activity, CancellationToken token)
{
var message = activity as IMessageActivity;
if (message != null && !string.IsNullOrWhiteSpace(message.Text))
{
if (message.Text.ToLower().Equals("exit", StringComparison.InvariantCultureIgnoreCase))
{
return message.Text;
}
}
return null;
}
protected override bool HasScore(IActivity item, string state)
{
return state != null;
}
protected override double GetScore(IActivity item, string state)
{
return 1.0;
}
protected override async Task PostAsync(IActivity item, string state, CancellationToken token)
{
var message = item as IMessageActivity;
if (message != null)
{
var settingsDialog = new ExitDialog();
var interruption = settingsDialog.Void<object, IMessageActivity>();
this.task.Call(interruption, null);
await this.task.PollAsync(token);
}
}
protected override Task DoneAsync(IActivity item, string state, CancellationToken token)
{
return Task.CompletedTask;
}
}
GlobalMessageHandlersBotModule:
public class GlobalMessageHandlersBotModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder
.Register(c => new ExitScorable(c.Resolve<IDialogTask>()))
.As<IScorable<IActivity, double>>()
.InstancePerLifetimeScope();
}
}
Register the module:
Conversation.UpdateContainer(
builder =>
{
builder.RegisterModule(new ReflectionSurrogateModule());
builder.RegisterModule<GlobalMessageHandlersBotModule>();
});
Test result:
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