Getting function parameters from Roslyn completion API - c#

I was trying to use Completion API for building Autocomplete feature. The results are coming correctly but I am not able to get the method signatures.
For example: If I type "string.", API returns me all the methods like "Format","Compare" but after "string.Format(" it does not return all the possible method signatures like Format(String, Object), Format(String, Object[]) etc. Can someone help me in achieving that
var expression = "String.";
var code = String.Format(CultureInfo.InvariantCulture,
#"
Imports System
Sub Main(args As String[])
Dim typeUnderInvestigation As String = {0}
End sub
", expression);
var projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), "MyProject", "MyProject", LanguageNames.VisualBasic)
.WithMetadataReferences(new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
});
var project = workspace.AddProject(projectInfo);
var document = workspace.AddDocument(project.Id, "MyFile.cs", SourceText.From(code));
// position is the last occurrence of "Guid." in our test code
// in real life scenarios the editor surface should inform us
// about the current cursor position
var position = code.LastIndexOf(expression, StringComparison.InvariantCulture) + expression.Length;
var completionService = CompletionService.GetService(document);
var results = await completionService.GetCompletionsAsync(document, position);

Related

How set OrderBy on GroupedEnumerable

My result set is not sorting. How do I set up OrderBy for type System.Linq.GroupedEnumerable
I've converted an application from Core 1.1 to 2.2. Everything ported over fine except one piece of logic that:
1) takes a response from a service call maps it to a GroupedEnumerable
2) takes the grouped set and passes it to a function that maps it to an object of type System.Linq.Enumerable.SelectEnumerableIterator.
The resulting object is properly populated but not sorted. I have tried the order by in the function parameter call and as a separate process afterwards.
//response = {myService.myClient.SearchNominationsResponse}
//groupedSet = {System.Linq.GroupedEnumerable<ServiceClients.myClient.NominationObject, long>}
//result = {System.Linq.Enumerable.SelectEnumerableIterator<System.Linq.IGrouping<long, ServiceClients.myClient.NominationObject>, app.ViewModels.EvaluationSummary>}
public IEnumerable<EvaluationSummary> GetEvaluationSummaries(string sortBy, string sortOrder, Filter filter = null)
{
var request = Mapper.MapSearchNominationRequest(filter);
request.IsDetailed = false;
var response = myService.SearchNominationsAsync(request).GetAwaiter().GetResult();
var groupedSet = response.mySet.GroupBy(n => n.SetId);
// I get a proper result but it is not sorted
var result = groupedSet.Select(
g => Mapper.MapEvaluationSummary(
g.OrderBy(g2 => sortBy + " " + sortOrder)
.Last()));
// Still not sorting
result = result.OrderBy(r => sortBy + sortOrder);
return result;
}
public EvaluationSummary MapEvaluationSummary(SetObject setIn)
{
var eval = new EvaluationSummary
{
setDate = setIn.Date,
setId = setIn.Id,
setTypeId = setIn.TypeId,
setTypeDescription = setIn.TypeDescription,
setStatusId = setIn.StatusId,
setStatusDescription = setIn.StatusDescription,
setBy = setIn.Manager,
setEmployee = setIn.employee
};
}
So in my view I have columns that list Date, Id, setEmployee. I can click on these values to issue a sort pattern and I can see that the sortBy and sortOrder variables are being passed in with proper values but the sorting is not happening.
I expect 'William' to appear before 'Bill' and then Bill to appear before 'William' when toggling the employee column header in my view.
Based off of the previous answers, I'm still not sure if I can substitute a property name in the LINQ with a variable. To fix my problem and move on I've implemented JS logic to sort my table headers. We had a custom JS that we use to format tables in our apps but it seems the sort functionality never worked. Anyway not an answer to my question but this is how I solved the problem:
Logic can be found at:
http://www.kryogenix.org/code/browser/sorttable/
-HaYen

Gremlin .Net, filter vertices by property containing a value

I need to filter vertices in Azure Cosmos Graph DB by a property containing a value, I tried the code below but I am getting an error says (Unable to find any method 'filter')
var g = client.CreateTraversalSource();
var p = new P("containing", text);
var query = g.V().Filter(p).Range<Vertex>(page, pageSize);
var result = await client.ExcuteAsync<IEnumerable<Vertex>>(query);
Any idea how to achieve this?
This might help someone else, I managed to figure it out with some help of a friend:
var p = new P("containing", text);
var query = g.V().has("propertyName", p).Range<Vertex>(page, pageSize);
var result = await client.ExecuteAsync<IEnumerable<Vertext>>(query);
In case anyone is still looking into this, there's predefined predicate values that can be used as string filters in the class TextP.
The above can be accomplished with the following:
var query = g.V().has("propertyName", TextP.Containing(text)).Range<Vertex>(page, pageSize);
var result = await client.ExecuteAsync<IEnumerable<Vertext>>(query);

Interpolated strings stored in a variable [duplicate]

Can one store the template of a string in a variable and use interpolation on it?
var name = "Joe";
var template = "Hi {name}";
I then want to do something like:
var result = $template;
The reason is my templates will come from a database.
I guess that these strings will have always the same number of parameters, even if they can change. For example, today template is "Hi {name}", and tomorrow could be "Hello {name}".
Short answer: No, you cannot do what you have proposed.
Alternative 1: use the string.Format method.
You can store in your database something like this:
"Hi {0}"
Then, when you retrieve the string template from the db, you can write:
var template = "Hi {0}"; //retrieved from db
var name = "Joe";
var result = string.Format(template, name);
//now result is "Hi Joe"
With 2 parameters:
var name2a = "Mike";
var name2b = "John";
var template2 = "Hi {0} and {1}!"; //retrieved from db
var result2 = string.Format(template2, name2a, name2b);
//now result2 is "Hi Mike and John!"
Alternative 2: use a placeholder.
You can store in your database something like this:
"Hi {name}"
Then, when you retrieve the string template from the db, you can write:
var template = "Hi {name}"; //retrieved from db
var name = "Joe";
var result = template.Replace("{name}", name);
//now result is "Hi Joe"
With 3 parameters:
var name2a = "Mike";
var name2b = "John";
var template2 = "Hi {name2a} and {name2b}!"; //retrieved from db
var result2 = template2
.Replace("{name2a}", name2a)
.Replace("{name2b}", name2b);
//now result2 is "Hi Mike and John!"
Pay attention at which token you choose for your placeholders. Here I used surrounding curly brackets {}. You should find something that is unlikely to cause collisions with the rest of your text. And that depends entirely on your context.
This can be done as requested using dynamic compilation, such as through the Microsoft.CodeAnalysis.CSharp.Scripting package. For example:
var name = "Joe";
var template = "Hi {name}";
var result = await CSharpScript.EvaluateAsync<string>(
"var name = \"" + name + "\"; " +
"return $\"" + template + "\";");
Note that this approach is slow, and you'd need to add more logic to handle escaping of quotes (and injection attacks) within strings, but the above serves as a proof-of-concept.
No you can't do that since it needs name value at the time string is created (compile time). Consider using String.Format or String.Replace instead.
I just had the same need in my app so will share my solution using String.Replace(). If you're able to use LINQ then you can use the Aggregate method (which is a reducing function, if you're familiar with functional programming) combined with a Dictionary that provides the substitutions you want.
string template = "Hi, {name} {surname}";
Dictionary<string, string> substitutions = new Dictionary<string, string>() {
{ "name", "Joe" },
{ "surname", "Bloggs" },
};
string result = substitutions.Aggregate(template, (args, pair) =>
args.Replace($"{{{pair.Key}}}", pair.Value)
);
// result == "Hi, Joe Bloggs"
This works by starting with the template and then iterating over each item in the substitution dictionary, replacing the occurrences of each one. The result of one Replace() call is fed into the input to the next, until all substitutions are performed.
The {{{pair.Key}}} bit is just to escape the { and } used to find a placeholder.
This is pretty old now, but as I've just come across it it's new to me!
It's a bit overkill for what you need, but I have used Handlebars.NET for this sort of thing.
You can create quite complex templates and merge in hierarchical data structures for the context. There's rules for looping and conditional sections, partial template compositing and even helper function extension points. It also handles many data types gracefully.
There's way too much to go into here, but a short example to illustrate...
var source = #"Hello {{Guest.FirstName}}{{#if Guest.Surname}} {{Guest.Surname}}{{/if}}!";
var template = Handlebars.Compile(source);
var rec = new {
Guest = new { FirstName = "Bob", Surname = null }
};
var resultString = template(rec);
In this case the surname will only be included in the output if the value is not null or empty.
Now admittedly this is more complicated for users than simple string interpolation, but remember that you can still just use {{fieldName}} if you want to, just that you can do lots more as well.
This particular nuGet is a port of HandlebarsJs so it has a high degree of compatibility. HandlebarsJs is itself a port of Mustache - there are direct dotNet ports of Mustache but IMHO HandlebarsNET is the business.

web api call with checking part of the URL using UriTemplate not working correctly

Perhaps I do not understand how to properly use the UriTemplate
I always want to search the incoming url for
api/whatever // that is it
If URL is like below, I want to ONLY get api/CheckMainVerified
So I suppose without using UriTemplate, I guess a substring check or regex would work ?
http://localhost:29001/api/CheckMainVerified/223128
This is what I was doing
var url = "http://localhost:29001/api/CheckMainVerified/223128";
//var host = new Uri(serverHost.AbsoluteUri);
var host = new Uri("http://localhost:29001");
var apiTemplate = new UriTemplate("{api}/{*params}", true);
var match = apiTemplate.Match(host, new Uri(url));
var finalSearch = match.BoundVariables["api"];
string parameters = match.BoundVariables["params"];
finalSearch.Dump();
parameters.Dump();
match.Dump();
I think you're just missing 2nd option in the template (see example below).
var template = new UriTemplate("{api}/{controller}/{*params}", true);
var fullUri = new Uri("http://localhost:29001/api/CheckMainVerified/223128");
var prefix = new Uri("http://localhost:29001");
var results = template.Match(prefix, fullUri);
if (results != null)
{
foreach (string item in results.BoundVariables.Keys)
{
Console.WriteLine($"Key: {item}, Value: {results.BoundVariables[item]}");
// PRODUCES:
// Key: API, Value: api
// Key: CONTROLLER, Value: CheckMainVerified
// Key: PARAMS, Value: 223128
}
Console.WriteLine($"{results.BoundVariables["api"]}/{results.BoundVariables["controller"]}");
// PRODUCES:
// api/CheckMainVerified
}
As such, adding the "{controller}" to the template should resolve your issue.
From there, you could do a simple string comparison.
i.e.
if ($"{results.BoundVariables["api"]}/{results.BoundVariables["controller"]}" == "api/CheckMainVerified") { ... }
Alternatively, as you suggested; you could use a substring or regex solution.

How to generate initialization of class fields with Roslyn

I know how to create a local variable inside a method, for example this:
LocalDeclarationStatement(VariableDeclaration(IdentifierName("MyClass"))
.WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier("nameOfvariable"))
.WithInitializer(
EqualsValueClause(
ObjectCreationExpression(IdentifierName("MyClass")).WithArgumentList(arguments)
.WithNewKeyword(Token(SyntaxKind.NewKeyword)))))));
would give me:
MyClass nameOfvariable = new MyClass();
But say that I already created a field and now I simply want to initialize it (in a method, constructor or anything) like this:
nameOfVariable = new MyClass();
How do I do this? My guess it have to do with the VariableDeclerator but I can't find a way to get it right so I can add it to a list that contains StatementSyntaxes. I can change the VariableDecleration to "VariableDeclaration(IdentifierName(""))" too but that gives me an ugly extra space infront of the statement.
It seems like I struggle with some really basic stuff of Roslyn and I try to check http://roslynquoter.azurewebsites.net/ but that feels like the forced way to do it (feels like it create a lot more code than necessary).
Update: Should clarify that I know how to create method/constructors. I'm only looking for a way to initialize a field when I only have access to the field name and field type. So the only code I want to generate is this:
myField = new MyField();
Well you're almost there, you just need to create all that. This should do what you're interested in:
const string source = #"
using System;
class MyClass
{
void Method()
{
MyClass nameOfVariable;
}
}
";
var tree = CSharpSyntaxTree.ParseText(source);
var compilation = CSharpCompilation.Create("MyCompilation", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var local = root.DescendantNodes().OfType<LocalDeclarationStatementSyntax>().First();
var declaration = local.Declaration;
var declarator = declaration.Variables.First();
var identifier = SyntaxFactory.IdentifierName("MyClass");
var objectCreationExpression = SyntaxFactory.ObjectCreationExpression(identifier, SyntaxFactory.ArgumentList(), null);
var equalsValueClause = SyntaxFactory.EqualsValueClause(objectCreationExpression);
var newDeclarator = declarator.WithInitializer(equalsValueClause).WithAdditionalAnnotations(Formatter.Annotation);
var newRoot = root.ReplaceNode(declarator, newDeclarator);
var formattedRoot = Formatter.Format(newRoot, Formatter.Annotation, new AdhocWorkspace());
Console.WriteLine(formattedRoot.GetText());
Console.Read();
Some explanation: you create a new identifier MyClass which will be used in your ObjectCreationExpression. Then you wrap all that in an EqualsValueClause and you set that as an initializer to your declarator. We also add the Formatter annotation to this node so we can format it later and don't end up with whitespace issues.
All that's left then is replacing the node in your original tree, formatting it and you're done:
-------------------------------------------------------------------------------
If you instead mean that you want to put the assignment on its own separately from the declaration then you have to create a new AssignmentExpression and wrap it inside a ExpressionStatement. Typically expressions and statements are distinct concepts but this ExpressionStatement allows us to treat an expression as a statement which is important because a method's body only accepts statements.
In code, it looks like this:
internal static void Execute()
{
const string source = #"
using System;
class MyClass
{
void Method()
{
MyClass nameOfVariable, another;
}
}
";
var tree = CSharpSyntaxTree.ParseText(source);
var compilation = CSharpCompilation.Create("MyCompilation", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var local = root.DescendantNodes().OfType<LocalDeclarationStatementSyntax>().First();
var method = local.Ancestors().OfType<MethodDeclarationSyntax>().First();
var variableIdentifier = SyntaxFactory.IdentifierName("nameOfVariable");
var classIdentifier = SyntaxFactory.IdentifierName("MyClass");
var objectCreationExpression = SyntaxFactory.ObjectCreationExpression(classIdentifier, SyntaxFactory.ArgumentList(), null);
var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, variableIdentifier, objectCreationExpression);
var expressionStatement = SyntaxFactory.ExpressionStatement(assignment).WithAdditionalAnnotations(Formatter.Annotation);
var newMethod = method.AddBodyStatements(expressionStatement);
var newRoot = root.ReplaceNode(method.Body, newMethod.Body);
var formattedRoot = Formatter.Format(newRoot, Formatter.Annotation, new AdhocWorkspace());
Console.WriteLine(formattedRoot.GetText());
Console.Read();
}
Result:
After some more trying and looking I found the answer. There is something called "AssignmentExpression" that you can use.
Here is an example how to use it:
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName("myField"),
ObjectCreationExpression(IdentifierName("MyClass")).WithArgumentList(arguments)
.WithNewKeyword(Token(SyntaxKind.NewKeyword))));
This would give you:
myField = new Myclass();
So now it's easy to seperate creation and assignment/initialization to two different statements.
Note that I'm using "using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;" so I don't have to write SyntaxFactory all the time.
Or you can goto "http://roslynquoter.azurewebsites.net/" and paste your code in the small little textbox and click "Get Roslyn API calls to generate this code".
(I can generate the code you posted above, but it is kinda long. so i use a simple example.
For example, let's say you paste "DateTime mydate2 = new DateTime()", the tool will generate the following code :-
LocalDeclarationStatement(
VariableDeclaration(
IdentifierName("DateTime"))
.WithVariables(
SingletonSeparatedList<VariableDeclaratorSyntax>(
VariableDeclarator(
Identifier("mydate2"))
.WithInitializer(
EqualsValueClause(
ObjectCreationExpression(
IdentifierName("DateTime"))
.WithArgumentList(
ArgumentList())))))).WithSemicolonToken(
MissingToken(SyntaxKind.SemicolonToken)).NormalizeWhitespace()
Then you just have to fix up the code using SyntaxFactory, for example :-
var myDeclaratyion = SyntaxFactory.LocalDeclarationStatement(
SyntaxFactory.VariableDeclaration(
SyntaxFactory.IdentifierName("DateTime")).
WithVariables(
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
SyntaxFactory.VariableDeclarator(
SyntaxFactory.Identifier("mydate2")).
WithInitializer(
SyntaxFactory.EqualsValueClause(
SyntaxFactory.ObjectCreationExpression(
SyntaxFactory.IdentifierName("DateTime"))
.WithArgumentList(
SyntaxFactory.ArgumentList())))))).WithSemicolonToken(SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken)).NormalizeWhitespace();

Categories