Extension method information from Roslyn - roslyn

I am trying to using Roslyn to extract various method call information from a give source file. To elaborate - I want to find all method invocations that happen inside the input file.
One problem I am hitting is to do with extension methods. Consider any Linq method like Aggregate, Sum etc. How can I figure out from an InvocationExpressionSyntax that the method being invoked is an extension method and not a simple member method of the class.
The source file I input can be expected to compile - meaning that GetDiagnostics() will not have any errors.
---- Some code to get to the property mentioned by #Kevin in his answer ----
var methodInfo = model.GetSymbolInfo(invocation);
if (methodInfo.Symbol != null)
{
var mSymbol = (IMethodSymbol)methodInfo.Symbol;
if (mSymbol.ReducedFrom != null)
{
// this is an extension method !
}
}

You can find the actual static extension method for an instance invocation of an extension method using the IMethodSymbol.ReducedFrom property.

Related

Mocking proto.Message in Go

I wrote a function that takes a list of proto.Message objects. Looking at the documentation, it seems like proto.Message wraps protoreflect.ProtoMessage which contains a single function, ProtoReflect() Message. Looking at the documentation for Message, it implements a number of other functions that return types referenced by the protoreflect package.
It seems that attempting to create a mock proto.Message would be a lot more work that it's worth but I don't want to go through the whole process of creating a protobuf file, compiling it and referencing it just for unit testing.
Is there another way I can create a mock proto.Message object?
After looking into this further, I decided that the best thing to do would be to create an actual protobuf message:
syntax = "proto3";
package my.package;
option go_package = "path/to/my/package"; // golang
message TestRecord {
string key = 1;
string value = 2;
}
and compile it into a Golang file and then modify it for my own purposes:
$ protoc --go_out=./gopb/ --go_opt=paths=source_relative *.proto
Once the test file has been created, I can delete it or save it as a record.

Javassist - addMethod that returns an Object[][]

My goal is to create in runtime an additional method inside a specific .class file.
A method that returns an Object[][].
For that I found an amazing framework called - Javassist, a bytecode modifier framework, which helps you modify your compiled class in runtime in order to add more bytecode that represents a new method.
Managed to create a void method, and a method that returns a string but, for some reason, I'm unable to generate a method that returns an array or a matrix.
So far I've been struggling to find the proper way of creating such method, and got a continuous CannotCompileException.
Code:
private static CtMethod generateMethod1(CtClass declaringClass)
throws CannotCompileException {
StringBuffer sb = new StringBuffer();
sb.append("public ").append(Object[][].class.getName()).append(" ").append("method1").append("(){")
.append("return new").append(Object[][].class.getName()).append("{{ 1,2 }}").append("; }");
System.out.println(sb.toString());
return CtMethod.make(sb.toString(), declaringClass);
}
The toString of the generated method above is:
public [[Ljava.lang.Object; method1(){return [[Ljava.lang.Object;{{ 1,2 }}; }
Probably fails due to false jni syntax.
Well, solved it by just replacing the Object[][].class.getName() with Object[][] literally...

Roslyn: SyntaxWalker through 2 different documents

I have a solution with two different projects. I use SyntaxWalker to process some stuff in ProjectA.Class1. However, ProjectA.Class1 has reference ProjectB.Class2.
Is there way to allow the syntax walker to traverse also through external classes? I can't even do it when both classes are in the same project but in different files (documents). It always goes through the same document. If both classes are in the same file then it works. If I extract them to separate ones, it doesn't...
I am working on a test coverage tool. A user click on the method in VS and then:
I use rewriter to add static variables to each branch.
I run the code so the static variables are set if branch was covered.
I wonder how should I configure a syntax walker\rewriter to recognize other classes in the same solution.
You're going to need access to the Symbol API. A simple rule of thumb I try to go by:
The Syntax API is for individual files
The Symbol API allows you to work with information that spans across files.
You've mentioned in the comments that you'd like to traverse methods and figure out some information about each method declaration. Here's some (naive) code that should get you started with the symbol API.
I've assumed you've got access to a Project that you're analyzing.
Project myProject;
public void ProcessMethod(MethodDeclarationSyntax method)
{
//Get the semantic model
var filePath = method.SyntaxTree.FilePath;
var containingDocument = myProject.Documents.Where(n => n.FilePath == filePath).Single();
var model = containingDocument.GetSemanticModelAsync().Result;
//...
//Do your processing on the current method here...
//...
//Process the invoked methods.
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach(var invocation in invocations)
{
var invokedSymbol = model.GetSymbolInfo(invocation).Symbol; //Might be null
var invokedSymbolSyntax = (MethodDeclarationSyntax)invokedSymbol.DeclaringSyntaxReferences.First().GetSyntax(); //Partial methods might be declared in multiple places
ProcessMethod(invokedSymbolSyntax);
}
}
Note:
This approach doesn't handle constructors, destructors, properties, expression-bodied members and any other members I've forgotten. But it should be enough to get you started and introduce you to the symbol API.
Recursion will bite you.
You won't process implementations of interfaces. You'll have to look into the SymbolFinder for that.

Creating a new instance from within CFC

Now this would seem to be something very straight forward, but seemingly not so in ColdFusion. I need to create an instance of a CFC from within itself as in var a = new this() but this obviously does not work. The CFC name can't be used as it is a base that will be extended so I am attempting a hack around the issue with the following:
component {
public function subQuery (required string table) {
var classPath = getMetaData(this).fullname;
return createObject("component", classPath).init(table, this.dsn);
}
}
This would be acceptable but the class path returned from getMetaData(this).fullname is incorrect. The CFC is within a folder named with a hypen as in my-folder and the returned path looks like my.-folder.myCFC with a period inserted before the hyphen. Obviously I could manipulate this string with a Regex but that is just not a road I want to go down.
Hoping someone has a cleaner approach, thanks.
You should be able to do it without any context on the object name in theory, as it will be being executed from within itself and it should check its current directory.
The following should therefore do the job you need
var classPath = ListLast(getMetaData(this).fullname,'.');
return createObject("component", classPath).init(table, this.dsn);
This way it doesn't matter what the directory names are, and it will work on any objects that extend that one regardless of directory structure, or for a complete example
public function cloneMe() {
return CreateObject('component', ListLast(getMetaData(this).fullname,'.')).init(argumentCollection=arguments);
}
This way any arguments passed in will be passed through into the init. I.e. an extending CFC may redefine the method as the following (if you want errors when the init arguments aren't supplied)
public function cloneMe(required string table) {
return super.cloneMe(table=arguments.table,dsn=this.dsn);
}

Storing an INI file in memory

I am writing an application that uses an ini file to store all status codes (errors, success codes, etc.) A very simple version is this:
[success]
000=Status code not found.
[error]
000=Error code not found.
001=Username/Password not found.
And my CF Component to work with that uses the following code:
component hint="Set of tools to interact with status codes."{
public function init(string codefile) any{
this.codefile = Arguments.codefile;
}
public function getCodeString(string type, string code) string{
var code = getProfileString(Variables.codefile, Arguments.type, Arguments.code);
return code;
}
}
What I assume happens when I call the getProfileString is that Railo opens the file, searches for the key and returns the value. So as my application grows and I have a lot more codes, I expect that this process will slow down. So is there a way that I can open the file in my init method and read it all into the variables scope, and call the getProfileString from there?
You can even parse your ini file in onApplicationStart and push the data into application scope like recommended for a XML file by #Sergii, if you want to stick with the .ini approach.
Do something like that:
var sections = getProfileSections(variables.codeFile);
var sectionEntries = [];
var indx = 0;
for (key in sections){
sectionEntries = listToArray(sections[key]);
application[key] = {};
for (indx=1; indx <= arraylen(sectionEntries); indx++){
application[key][sectionEntries[indx]] = getProfileString(variables.cfgFile,key,sectionEntries[indx]);
}
}
haven't tested this on Railo, but it should work on ColdFusion 9 at least
Because you are using Railo there's possibly easiest solution: put the file into the RAM filesystem.
So full path to the file will look like ram:///some/path/to/config.ini.
Obviously, you need to write the file into the RAM first, possibly on first request.
So slightly modified version of the component may look this way:
component hint="Set of tools to interact with status codes."{
public function init(string codefile, string.ramfile) any{
variables.codefile = arguments.codefile;
variables.ramfile = arguments.ramfile;
}
public function getCodeString(string type, string code) string{
if (NOT fileExists(variables.ramfile)) {
fileCopy(variables.codefile, variables.ramfile);
}
return getProfileString(variables.ramfile, arguments.type, arguments.code);
}
}
Please note that I've changed this.codefile to the variables.codefile in the init.
Any way, I'm also not sure ini file is the most handy and maintainable solution. You need to parse it each time any way, right? If you need a file config, use XML. Just parse it in onApplicationStart and push the data into the application scope.