How to write a DiagnosticAnalyzer for razor files? - c#

My Goal
My team decided, that they wanted to incorporate automation to enforce coding guidelines in .razor files.
Since we're already using StyleCopAnalyzers, I might implement our own Analyzer to achieve this goal.
Example
To better make you understand what's in my mind, consider the following example.
Let the following be valid code:
<MyGrid>
<MyItem></MyItem>
<MyItem></MyItem>
<MyItem></MyItem>
</MyGrid>
Now, If someone wouldn't encapsulate <MyItem> within <MyGrid> it would cause the Analyzer to report a diagnostic like "MyItem should be direct child of a MyGrid".
What I tried
Firstly, I followed microsoft's tutorial: Write your first analyzer and code fix. This worked perfectly well.
However, my next step was to create a Sample Blazor WASM Application using the default template. When I executed the MakeConstAnalyzer on the Sample Blazor Application, I'd get the following behavior.
Modifying Program.cs like this:
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
// I get squiggly lines here with the expected analyzer message from the MakeConstAnalyzer
int bla = 0;
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
}
}
But modifying Counter.razor like this:
private void IncrementCount()
{
int bla = 0; // add this line
currentCount++;
}
now, this won't show the analyzers message. Of course this will
trigger a compiler warning, but mainly this tells me, that for some
reason the Analyzer isn't executed on the Counter.razor file.
The Microsoft.CodeAnalysis.Razor Package
I thought, I'd mention it here. I found it on Nuget and tried to integrate it in the sample.
Caution
I have no clue, If the purpose of the package is even remotely connected, to what I want to achieve, since I really didn't find any documentation.
Since the microsoft tutorial clearly states, that any Analyzer must in some way inherit DiagnosticAnalyzer, I also checked the package for an implementation. But in both, the Microsoft.CodeAnalysis.Razor and Microsoft.AspNetCore.Razor.Language Package there is no class, that inherits from DiagnosticAnalyzer.
I've also tried to decorate my Analyzer, to specify, that it should also parse *.razor files, like that:
[DiagnosticAnalyzer(LanguageNames.CSharp, Microsoft.CodeAnalysis.Razor.RazorLanguage.Name)]
Summary
At this point, I don't know, what else to try. Also I didn't find anything remotely like that in the web.
These are my Questions:
How can I instruct my Analyzer to also parse .razor files?
Also how would I access the razor code in that approach?
I can imagine to either use Analyzers Syntax to traverse the Component Tree, but I think I could realize most of my usecases by only having the plain unparsed code.

Related

Powershell implement C# System.Diagnostics.Tracing.EventSource

So maybe I am just ignorant, but C# has some lovely functionality with System.Diagnostics.Tracing.EventSource that make writing new event sources super simple. I've got some functionality built up around this convenient api that allows these logs to be processed after ingestion.
All of this however is being moved to powershell and I'm trying to get it working. If I consume a current implementation of a EventSource subclass through dlls everything works as expected. If I try to write a new EventSource subclass purely in powershell, the event stop showing up.
I have researched the googles quite thoroughly and it has become clear to me that this is not a standard approach. (Surprising because it is such a handy chunk of coding) Can anyone help or tell me what I may be doing wrong?
class TestEventSource2 : System.Diagnostics.Tracing.EventSource
{
static [TestEventSource2] $Log = [TestEventSource2]::New()
static [string] $LogName = "its a name"
[void] WriteTestEvent() {
Write-Information "WriteTestEvent"
WriteEvent(1,"Hello world from a powershell test event.")
}
}
$loglog = [TestEventSource2]::Log
$loglog.WriteTestEvent()

How to output a file from a roslyn code analyzer?

I'm using the roslyn API to write a DiagnosticAnalyzer and CodeFix.
After I have collected all strings and string-interpolations, I want to write all of them to a file but I am not sure how to do this the best way.
Of course I can always simply do a File.WriteAllText(...) but I'd like to expose more control to the user.
I'm also not sure about how to best trigger the generation of this file, so my questions are:
I do not want to hard-code the filename, what would be the best way to expose this setting to the user of the code-analyzer? A config file? If so, how would I access that? ie: How do I know the directory?
If one string is missing from the file, I'd like to to suggest a code fix like "Project contains changed or new strings, regenerate string file". Is this the best way to do this? Or is it possible to add a button or something to visual studio?
I'm calling the devenv.com executable from the commandline to trigger builds, is there a way to force my code-fix to run either while building, or before/after? Or would I have to "manually" load the solution with roslyn and execute my codefix?
I've just completed a project on this. There are a few things that you will need to do / know.
You will probably need to switch you're portable class library to a class library. otherwise you will have trouble calling the File.WriteAllText()
You can see how to Convert a portable class library to a regular here
This will potentially not appropriately work for when trying to apply all changes to document/project/solution. When Calling from a document/project/solution, the changes are precalcuated and applied in a preview window. If you cancel, an undo action is triggered to undo all changes, if you write to a file during this time, and do not register an undo action you will not undo the changes to the file.
I've opened a bug with roslyn but you can handle instances by override the preview you can see how to do so here
And one more final thing you may need to know is how to access the Solution from the analyzer which, Currently there is a hack I've written to do so here
As Tamas said you can use additional files you can see how to do so here
You can use additional files, but I know on the version I'm using resource files, are not marked as additional files by default they are embeddedResources.
So, for my users to not have to manually mark the resource as additonalFiles I wrote a function to get out the Designer.cs files associated with resource files from the csproj file using xDoc you can use it as an example if you choose to parse the csproj file:
protected List<string> GetEmbeddedResourceResxDocumentPaths(Project project)
{
XDocument xmldoc = XDocument.Load(project.FilePath);
XNamespace msbuild = "http://schemas.microsoft.com/developer/msbuild/2003";
var resxFiles = new List<string>();
foreach (var resource in xmldoc.Descendants(msbuild + "EmbeddedResource"))
{
string includePath = resource.Attribute("Include").Value;
var includeExtension = Path.GetExtension(includePath);
if (0 == string.Compare(includeExtension, RESX_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase))
{
var outputTag = resource.Elements(msbuild + LAST_GENERATED_TAG).FirstOrDefault();
if (null != outputTag)
{
resxFiles.Add(outputTag.Value);
}
}
}
return resxFiles;
}
For config files you can use the AdditionalFiles msbuild property, which is passed to the analyzers through the context. See here.

Is it possible to use Gephi compiled with IKVM in a website?

I'm currently trying to load and use the Gephi Toolkit from within a .Net 4 C# website.
I have a version of the toolkit jar file compiled against the IKVM virtual machine, which works as expected from a command line application using the following code:
var controller = (ProjectController)Lookup.getDefault().lookup(typeof(ProjectController));
controller.closeCurrentProject();
controller.newProject();
var project = controller.getCurrentProject();
var workspace = controller.getCurrentWorkspace();
The three instances are correctly instantiated in a form similar to org.gephi.project.impl.ProjectControllerImpl#8ddb93.
If however I run the exact same code, with the exact same using statements & references, the very first line loading the ProjectController instance returns null.
I have tried a couple of solutions
Firstly, I have tried ignoring the Lookup.getDefault().lookup(type) call, instead trying to create my own instances:
var controller = new ProjectControllerImpl();
controller.closeCurrentProject();
controller.newProject();
var project = controller.getCurrentProject();
var workspace = controller.getCurrentWorkspace();
This fails at the line controller.newProject();, I think because internally (using reflector) the same Lookup.getDefault().lookup(type) is used in a constructor, returns null and then throws an exception.
Secondly, from here: Lookup in Jython (and Gephi) I have tried to set the %CLASSPATH% to the location of both the toolkit JAR and DLL files.
Is there a reason why the Lookup.getDefault().lookup(type) would not work in a web environment? I'm not a Java developer, so I am a bit out of my depth with the Java side of this.
I would have thought it possible to create all of the instances myself, but haven't been able to find a way to do so.
I also cannot find a way of seeing why the ProjectController load returned null. No exception is thrown, and unless I'm being very dumb, there doesn't appear to be a method to see the result of the attempted load.
Update - Answer
Based on the answer from Jeroen Frijters, I resolved the issue like this:
public class Global : System.Web.HttpApplication
{
public Global()
{
var assembly = Assembly.LoadFrom(Path.Combine(root, "gephi-toolkit.dll"));
var acl = new AssemblyClassLoader(assembly);
java.lang.Thread.currentThread().setContextClassLoader(new MySystemClassLoader(acl));
}
}
internal class MySystemClassLoader : ClassLoader
{
public MySystemClassLoader(ClassLoader parent)
: base(new AppDomainAssemblyClassLoader(typeof(MySystemClassLoader).Assembly))
{ }
}
The code ikvm.runtime.Startup.addBootClassPathAssemby() didn't seem to work for me, but from the provided link, I was able to find a solution that seems to work in all instances.
This is a Java class loader issue. In a command line app your main executable functions as the system class loader and knows how to load assembly dependencies, but in a web process there is no main executable so that system class loader doesn't know how to load anything useful.
One of the solutions is to call ikvm.runtime.Startup.addBootClassPathAssemby() to add the relevant assemblies to the boot class loader.
For more on IKVM class loading issues see http://sourceforge.net/apps/mediawiki/ikvm/index.php?title=ClassLoader

Run javascript in C# WPF Form

*Please note that this is not for a web based application, it's windows based.
I'm building an application where I will need the user to submit simple javascripts that will be run by the application.
The scripts will call functions that are part of the c# build.
An example:
C# code:
public void helloWorld()
{
Debug.WriteLine("hello world");
}
Javascript submitted by user:
helloWorld();
The JavaScript would be parsed by the application at runtime and then call the required functions in my C# code.
Why?..
My app will be used by people with very little programming experience, they enter very simple JavaScripts and the app will attempt to automate a few tasks on the users computer. So my reason for using JavaScript is because it's simple and very easy to learn for someone with little experience.
It sounds like you want a JavaScript parser for your application. To be honest, I dont think what you're doing is possible, considering the context of the script and your code is different. However, this project seems to be doing something that may get you to the right place:
http://javascriptdotnet.codeplex.com/
Personally, I would think making some kind of XML format would be useful (like how UrlRewriter.net makes rewriting URLs easy):
<xml>
<commands>
<!-- Expose a Set of Condition Objects to Select From -->
<if condition="YourApplication.Conditions.RightClickOnDesktop">
<print text="HelloWorld" />
</if>
</commands>
Here is an example running a javascript code which, in turn, invokes a c# method
[System.Runtime.InteropServices.ComVisible(true)]
public class CSharpClass
{
public void MsgBox(string s)
{
MessageBox.Show(s);
}
}
-
Type scriptType = Type.GetTypeFromCLSID(Guid.Parse("0E59F1D5-1FBE-11D0-8FF2-00A0D10038BC"));
dynamic obj = Activator.CreateInstance(scriptType, false);
obj.Language = "Javascript";
obj.AddObject("mywindow", new CSharpClass(), true);
var result = obj.Eval(
#"
function test(){
mywindow.MsgBox('hello');
}
test();
"
);
Why do you "need the user to submit simple javascripts"? What is your application and what do users need it to do? Why have you decided a scripting language is the way to do this? I'm not saying that is the wrong answer, but that you have not justified this conclusion.
If your app will be used by "people with very little programming experience" I do not recommend implementing a scripting language. Basic concepts like source code and variables are very difficult for non-programmers to understand.
I suggest first investigating macro recording for user scripting. For .NET there is UI Automation and the White automation framework.

Why do 'requires' statements fail when loading (iron)ruby script via a C# program?

IronRuby and VS2010 noob question:
I'm trying to do a spike to test the feasibility of interop between a C# project and an existing RubyGem rather than re-invent that particular wheel in .net. I've downloaded and installed IronRuby and the RubyGems package, as well as the gem I'd ultimately like to use.
Running .rb files or working in the iirb Ruby console is without problems. I can load the both the RubyGems package, and the gem itself and use it, so, at least for that use case, my environment is set up correctly.
However, when I try to do the same sort of thing from within a C# (4.0) console app, it complains about the very first line:
require 'RubyGems'
With the error:
no such file to load -- rubygems
My Console app looks like this:
using System;
using IronRuby;
namespace RubyInteropSpike
{
class Program
{
static void Main(string[] args)
{
var runtime = Ruby.CreateRuntime();
var scope = runtime.ExecuteFile("test.rb");
Console.ReadKey();
}
}
}
Removing the dependencies and just doing some basic self-contained Ruby stuff works fine, but including any kind of 'requires' statement seems to cause it to fail.
I'm hoping that I just need to pass some additional information (paths, etc) to the ruby runtime when I create it, and really hoping that this isn't some kind of limitation, because that would make me sad.
Short answer: Yes, this will work how you want it to.You need to use the engine's SetSearchPaths method to do what you wish.
A more complete example
(Assumes you loaded your IronRuby to C:\IronRubyRC2 as the root install dir)
var engine = IronRuby.Ruby.CreateEngine();
engine.SetSearchPaths(new[] {
#"C:\IronRubyRC2\Lib\ironruby",
#"C:\IronRubyRC2\Lib\ruby\1.8",
#"C:\IronRubyRC2\Lib\ruby\site_ruby\1.8"
});
engine.Execute("require 'rubygems'"); // without SetSearchPaths, you get a LoadError
/*
engine.Execute("require 'restclient'"); // install through igem, then check with igem list
engine.Execute("puts RestClient.get('http://localhost/').body");
*/
Console.ReadKey();

Categories