How to collect all MethodDeclarationSyntax transitively with Roslyn? - c#

Given a list of MethodDeclarationSyntax I would like to collect all the methods in a solution that are calling this method transitively.
I have been using the following code:
var methods = new Stack<MethodDeclarationSyntax>();
... // fill methods with original method to start from
var visited = new HashSet<MethodDeclarationSyntax>();
while (methods.Count > 0)
{
var method = methods.Pop();
if (!visited.Add(method))
{
continue;
}
var methodSymbol = (await solution.GetDocument(method.SyntaxTree).GetSemanticModelAsync()).GetDeclaredSymbol(method);
foreach (var referencer in await SymbolFinder.FindCallersAsync(methodSymbol, solution))
{
var callingMethod = (MethodDeclarationSyntax) referencer.CallingSymbol.DeclaringSyntaxReferences[0].GetSyntax();
methods.Push(callingMethod);
}
}
The problem is that MethodDeclarationSyntax doesn't seem to be a singleton, so this loop is running forever, visiting the same methods again and again.
What is the proper way to uniquely identify a MethodDeclarationSyntax in a Dictionary/Hashset?
Edit 1)
As a workaround, I'm using the following MethodDeclarationSyntaxComparer to initialize my HashSet, but it looks very fragile:
private class MethodDeclarationSyntaxComparer: IEqualityComparer<MethodDeclarationSyntax>
{
public bool Equals(MethodDeclarationSyntax x, MethodDeclarationSyntax y)
{
var xloc = x.GetLocation();
var yloc = y.GetLocation();
return xloc.SourceTree.FilePath == yloc.SourceTree.FilePath &&
xloc.SourceSpan == yloc.SourceSpan;
}
public int GetHashCode(MethodDeclarationSyntax obj)
{
var loc = obj.GetLocation();
return (loc.SourceTree.FilePath.GetHashCode() * 307) ^ loc.SourceSpan.GetHashCode();
}
}

I'm wondering whether using SyntaxNode here is the right way to go.
Since you're already using SymbolFinder and you're using the semantic model, maybe the right way to go is to actually use ISymbols, rather than SyntaxNodes.
ISymbol already contains the SyntaxReferences you are using, so:
var methods = new Stack<IMethodSymbol>();
... // fill methods with original method to start from
... // convert methods to symbols via semanticModel.GetDeclaredSymbol (node);
var visited = new HashSet<IMethodSymbol>();
while (methods.Count > 0)
{
var method = methods.Pop();
if (!visited.Add(method))
{
continue;
}
foreach (var referencer in await SymbolFinder.FindCallersAsync(method, solution))
{
var callingMethod = (MethodDeclarationSyntax) referencer.CallingSymbol.DeclaringSyntaxReferences[0].GetSyntax();
methods.Push(callingMethod);
}
}
You could possibly make the visited hashset into a Dictionary<IMethodSymbol, IEnumerable<Location>>, and concat all the locations, and thus reconstruct the syntaxes from that result.

Related

Merging Two .CS Files in C# to generate a new Class

I want to merge two .cs files to create a third one. Can anyone help me please.
public partial class A
{
// some methods
}
suppose this code is written in a file A.cs
public partial class B
{
// some methods
}
and this code is written in a file B.cs.
I want to generate a new C.cs
having all the code of A.cs and B.cs ignoring namespaces.
I assume you indeed want to merge the partial definitions of the same class. If you really need to merge different classes into a single one, the code can be easily adjusted, but there will be no guarantee that it compiles (because, for example, the classes could have members with the same name).
The problem is quite complicated indeed because of the symbol meaning: it depends on the usings, so one needs to be really careful when merging them.
So the best idea would be not to try to analyse the code semantics manually, but to use a big hammer: Roslyn analyzer.
Let's start.
First of all, you'll need to install Extension Development Workload as it's described here. After this, you'll be able to create a Standalone code analysis tool project.
When you create it, you'll get a lot of useful boilerplate code like this:
class Program
{
static async Task Main(string[] args)
{
// ...
using (var workspace = MSBuildWorkspace.Create())
{
var solutionPath = args[0];
WriteLine($"Loading solution '{solutionPath}'");
var solution = await workspace.OpenSolutionAsync(solutionPath,
new ConsoleProgressReporter());
WriteLine($"Finished loading solution '{solutionPath}'");
// insert your code here
}
}
private static VisualStudioInstance SelectVisualStudioInstance(
VisualStudioInstance[] visualStudioInstances)
{
// ...
}
private class ConsoleProgressReporter : IProgress<ProjectLoadProgress>
{
// ...
}
}
Let's fill in what is needed.
Instead of // insert your code here let's put the following code:
var targetClass = args[1];
var modifiedSolution = await MergePartialClasses(targetClass, solution);
workspace.TryApplyChanges(modifiedSolution);
We'll need to implement the logic in MergePartialClasses. The name of the class should be passed as the second command line parameter.
Let's first add the following usings at the top:
using static System.Console;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
Now we can start with the main method. I've put the comments about what's happening directly in the code.
static async Task<Solution> MergePartialClasses(string targetClass, Solution solution)
{
// https://stackoverflow.com/a/32179708/276994
// we loop through the projects in the solution and process each of the projects
foreach (var projectId in solution.ProjectIds)
{
var project = solution.GetProject(projectId);
WriteLine($"Processing project {project.Name}");
var compilation = await project.GetCompilationAsync();
// finding the type which we want to merge
var type = compilation.GetTypeByMetadataName(targetClass);
if (type == null)
{
WriteLine($"Type {targetClass} is not found");
return solution;
}
// look up number of declarations. if it's only 1, we have nothing to merge
var declarationRefs = type.DeclaringSyntaxReferences;
if (declarationRefs.Length <= 1)
{
WriteLine($"Type {targetClass} has only one location");
return solution;
}
// I didn't implement the case of nested types, which would require to
// split the outer class, too
if (type.ContainingType != null)
throw new NotImplementedException("Splitting nested types");
// we'll accumulate usings and class members as we traverse all the definitions
var accumulatedUsings = new List<UsingDirectiveSyntax>();
var classParts = new List<ClassDeclarationSyntax>();
foreach (var declarationRef in declarationRefs)
{
var declaration = (ClassDeclarationSyntax)await declarationRef.GetSyntaxAsync();
// get hold of the usings
var tree = declaration.SyntaxTree;
var root = await tree.GetRootAsync();
var usings = root.DescendantNodes().OfType<UsingDirectiveSyntax>();
accumulatedUsings.AddRange(usings);
// since we are trying to move the syntax into another file,
// we need to expand everything in order to remove the dependency
// on usings
// in order to do it, we use a custom CSharpSyntaxRewriter (defined later)
var document = project.GetDocument(tree);
var expander = new AllSymbolsExpander(document);
var expandedDeclaration = (ClassDeclarationSyntax)expander.Visit(declaration);
classParts.Add(expandedDeclaration);
// remove the old declaration from the place where it is
// we can't just remove the whole file as it may contain some other classes
var modifiedRoot =
root.RemoveNodes(new[] { declaration }, SyntaxRemoveOptions.KeepNoTrivia);
var modifiedDocument = document.WithSyntaxRoot(modifiedRoot);
project = modifiedDocument.Project;
}
// now, sort the usings and remove the duplicates
// in order to use DistinctBy, I added MoreLinq nuget package and added
// using MoreLinq; at the beginning
// https://stackoverflow.com/a/34063289/276994
var sortedUsings = accumulatedUsings
.DistinctBy(x => x.Name.ToString())
.OrderBy(x => x.StaticKeyword.IsKind(SyntaxKind.StaticKeyword) ?
1 : x.Alias == null ? 0 : 2)
.ThenBy(x => x.Alias?.ToString())
.ThenByDescending(x => x.Name.ToString().StartsWith(nameof(System) + "."))
.ThenBy(x => x.Name.ToString());
// now, we have to merge the class definitions.
// split the name into namespace and class name
var (nsName, className) = SplitName(targetClass);
// gather all the attributes
var attributeLists = List(classParts.SelectMany(p => p.AttributeLists));
// modifiers must be the same, so we are taking them from the
// first definition, but remove partial if it's there
var modifiers = classParts[0].Modifiers;
var partialModifier = modifiers.FirstOrDefault(
m => m.Kind() == SyntaxKind.PartialKeyword);
if (partialModifier != null)
modifiers = modifiers.Remove(partialModifier);
// gather all the base types
var baseTypes =
classParts
.SelectMany(p => p.BaseList?.Types ?? Enumerable.Empty<BaseTypeSyntax>())
.Distinct()
.ToList();
var baseList = baseTypes.Count > 0 ? BaseList(SeparatedList(baseTypes)) : null;
// and constraints (I hope that Distinct() works as expected)
var constraintClauses =
List(classParts.SelectMany(p => p.ConstraintClauses).Distinct());
// now, we construct class members by pasting together the accumulated
// per-part member lists
var members = List(classParts.SelectMany(p => p.Members));
// now we can build the class declaration
var classDef = ClassDeclaration(
attributeLists: attributeLists,
modifiers: modifiers,
identifier: Identifier(className),
typeParameterList: classParts[0].TypeParameterList,
baseList: baseList,
constraintClauses: constraintClauses,
members: members);
// if there was a namespace, let's put the class inside it
var body = (nsName == null) ?
(MemberDeclarationSyntax)classDef :
NamespaceDeclaration(IdentifierName(nsName)).AddMembers(classDef);
// now create the compilation unit and insert it into the project
// http://roslynquoter.azurewebsites.net/
var newTree = CompilationUnit()
.WithUsings(List(sortedUsings))
.AddMembers(body)
.NormalizeWhitespace();
var newDocument = project.AddDocument(className, newTree);
var simplifiedNewDocument = await Simplifier.ReduceAsync(newDocument);
project = simplifiedNewDocument.Project;
solution = project.Solution;
}
// finally, return the modified solution
return solution;
}
The rest is the AllSymbolsExpander, which just calls Simplifier.ExpandAsync for every node:
class AllSymbolsExpander : CSharpSyntaxRewriter
{
Document document;
public AllSymbolsExpander(Document document)
{
this.document = document;
}
public override SyntaxNode VisitAttribute(AttributeSyntax node) =>
Expand(node);
public override SyntaxNode VisitAttributeArgument(AttributeArgumentSyntax node) =>
Expand(node);
public override SyntaxNode VisitConstructorInitializer(ConstructorInitializerSyntax node) =>
Expand(node);
public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node) =>
Expand(node);
public override SyntaxNode VisitXmlNameAttribute(XmlNameAttributeSyntax node) =>
Expand(node);
public override SyntaxNode VisitTypeConstraint(TypeConstraintSyntax node) =>
Expand(node);
public override SyntaxNode DefaultVisit(SyntaxNode node)
{
if (node is ExpressionSyntax ||
node is StatementSyntax ||
node is CrefSyntax ||
node is BaseTypeSyntax)
return Expand(node);
return base.DefaultVisit(node);
}
SyntaxNode Expand(SyntaxNode node) =>
Simplifier.ExpandAsync(node, document).Result; //? async-counterpart?
}
and the trivial function SplitName:
static (string, string) SplitName(string name)
{
var pos = name.LastIndexOf('.');
if (pos == -1)
return (null, name);
else
return (name.Substring(0, pos), name.Substring(pos + 1));
}
That's all!
I wanted to merge all code generated files to create one file. After a lot of searches I achieved this task by creating a new class. Read all code first generated files, create their objects in my newly created class and call their Up() and Down() methods in it.
Note: Wrote a separate method a read all namespaces distinct. If anyone wants code I can share my code sample too.

Roslyn get IdentifierName in ObjectCreationExpressionSyntax

Currently I am working on simple code analyse for c# with roslyn. I need to parse all document of all projects inside one solution and getting the declared used classes inside this document.
For example from:
class Program
{
static void Main(string[] args)
{
var foo = new Foo();
}
}
I want to get Program uses Foo.
I already parse all documents and get the declared class inside.
// all projects in solution
foreach (var project in _solution.Projects)
{
// all documents inside project
foreach (var document in project.Documents)
{
var syntaxRoot = await document.GetSyntaxRootAsync();
var model = await document.GetSemanticModelAsync();
var classes = syntaxRoot.DescendantNodes().OfType<ClassDeclarationSyntax>();
// all classes inside document
foreach (var classDeclarationSyntax in classes)
{
var symbol = model.GetDeclaredSymbol(classDeclarationSyntax);
var objectCreationExpressionSyntaxs = classDeclarationSyntax.DescendantNodes().OfType<ObjectCreationExpressionSyntax>();
// all object creations inside document
foreach (var objectCreationExpressionSyntax in objectCreationExpressionSyntaxs)
{
// TODO: Get the identifier value
}
}
}
}
The problem is to get the IdentifierName Foo. Using the debugger, I see objectCreationExpressionSyntax.Typegot the Identifier.Text got the value I need, but objectCreationExpressionSyntax.Type.Identifierseems to be private.
I could use the SymbolFinder to find all references of a Class in the solution. As I already need to parse all documents its should work without.
Maybe I am on the wrong path? How to get the identifier value?
You'll need to handle the different types of TypeSyntaxes. See here: http://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Syntax/TypeSyntax.cs,29171ac4ad60a546,references
What you see in the debugger is a SimpleNameSyntax, which does have a public Identifier property.
Update
var ns = objectCreationExpressionSyntax.Type as NameSyntax;
if (ns != null)
{
return ns.Identifier.ToString();
}
var pts = objectCreationExpressionSyntax.Type as PredefinedTypeSyntax;
if (pts != null)
{
return pts.Keyword.ToString();
}
...
All other subtypes would need to be handed. Note that ArrayType.ElementType is also a TypeSyntax, so you would most probably need to make this method recursive.
You can get the identifier from the syntax's Type property:
foreach (var objectCreationExpressionSyntax in objectCreationExpressionSyntaxs)
{
IdentifierNameSyntax ins = (IdentifierNameSyntax)objectCreationExpressionSyntax.Type;
var id = ins.Identifier;
Console.WriteLine(id.ValueText);
}
Strings can be misleading.
Let's say you have the expression new SomeClass(), and you get the string "SomeClass" out of it. How do you know if that refers to Namespace1.SomeClass or Namespace2.SomeClass ? What if there is a using SomeClass = Namespace3.SomeOtherType; declaration being used?
Fortunately, you don't have to do this analysis yourself. The compiler can bind the ObjectCreationExpressionSyntax to a symbol. You have your semantic model, use it.
foreach (var oce in objectCreationExpressionSyntaxs)
{
ITypeSymbol typeSymbol = model.GetTypeInfo(oce).Type;
// ...
}
You can compare this symbol with the symbols you get from model.GetDeclaredSymbol(classDeclarationSyntax), just make sure you use the Equals method, not the == operator.

How do I replace usages of a lambda parameter with another variable in a SimpleLambdaExpression?

I'm new to Roslyn. I'm trying to write an analyzer that detects when Select is being iterated through in a foreach loop, e.g.
foreach (TResult item in source.Select(x => x.Foo()))
{
...
}
and I'm writing a code fix provider that transforms such statements to
foreach (TSource __ in source)
{
TResult item = __.Foo();
...
}
Here is the code I have so far for the code fix provider. (It's somewhat long; the InlineSimpleLambdaExpressionAsync is where the meat of the changes are, but I included everything from RegisterCodeFixesAsync for context.)
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;
var selectInvocation = (InvocationExpressionSyntax)syntaxRoot.FindNode(diagnosticSpan);
var forEach = (ForEachStatementSyntax)selectInvocation.Parent;
context.RegisterCodeFix(
CodeAction.Create(
title: Title,
createChangedDocument: ct => InlineSelectorAsync(context.Document, forEach, selectInvocation, ct),
equivalenceKey: Title),
diagnostic);
}
private static Task<Document> InlineSelectorAsync(
Document document,
ForEachStatementSyntax forEach,
InvocationExpressionSyntax selectInvocation,
CancellationToken ct)
{
var selectorExpr = selectInvocation.ArgumentList.Arguments.Single().Expression;
switch (selectorExpr.Kind())
{
case SyntaxKind.SimpleLambdaExpression:
// This will be the most common case.
return InlineSimpleLambdaExpressionAsync(
document,
forEach,
selectInvocation,
(SimpleLambdaExpressionSyntax)selectorExpr,
ct);
}
return Task.FromResult(document);
}
private static async Task<Document> InlineSimpleLambdaExpressionAsync(
Document document,
ForEachStatementSyntax forEach,
InvocationExpressionSyntax selectInvocation,
SimpleLambdaExpressionSyntax selectorExpr,
CancellationToken ct)
{
var smodel = await document.GetSemanticModelAsync(ct).ConfigureAwait(false);
// First, change the foreach to iterate directly through the source enumerable,
// and remove the Select() method call.
// NOTE: GetSimpleMemberAccessExpression() is an extension method I wrote.
var sourceExpr = selectInvocation.GetSimpleMemberAccessExpression()?.Expression;
if (sourceExpr == null)
{
return document;
}
// Figure out the element type of the source enumerable.
var sourceTypeSymbol = smodel.GetTypeInfo(sourceExpr, ct).Type;
Debug.Assert(sourceTypeSymbol != null);
// NOTE: GetElementType is an extension method I wrote.
var elementTypeSymbol = sourceTypeSymbol.GetElementType(smodel);
// Now, update the foreach. Replace the element type of the selected enumerable
// with the element type of the source. Make '__' the identifier (TODO: Improve on this).
var ident = SyntaxFactory.Identifier("__");
int position = forEach.Type.SpanStart;
var elementTypeSyntax = SyntaxFactory.IdentifierName(elementTypeSymbol.ToMinimalDisplayString(smodel, position));
var newForEach = forEach
.WithType(elementTypeSyntax)
.WithIdentifier(ident)
.WithExpression(sourceExpr);
// Now, we have to take the selector and inline it.
var selectorBody = selectorExpr.Body as ExpressionSyntax;
Debug.Assert(selectorBody != null);
var selectorParam = selectorExpr.Parameter;
selectorBody = selectorBody.ReplaceNode(selectorParam, SyntaxFactory.IdentifierName("__")); // This doesn't work.
var selectorStatement = SyntaxFactory.LocalDeclarationStatement(
SyntaxFactory.VariableDeclaration(
type: forEach.Type,
variables: SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.VariableDeclarator(
identifier: forEach.Identifier,
argumentList: null,
initializer: SyntaxFactory.EqualsValueClause(selectorBody)))));
var forEachStatment = forEach.Statement as BlockSyntax;
// TODO: Consider supporting non-block statements? Would that happen with no braces?
if (forEachStatment == null)
{
return document;
}
newForEach = newForEach.WithStatement(
// NOTE: InsertStatements is an extension method I wrote.
forEachStatment.InsertStatements(0, selectorStatement));
// Update the syntax root and the document.
var syntaxRoot = await document.GetSyntaxRootAsync(ct).ConfigureAwait(false);
syntaxRoot = syntaxRoot.ReplaceNode(forEach, newForEach);
return document.WithSyntaxRoot(syntaxRoot);
}
When I run the code fix on the following code:
foreach (var item in array.Select(x => x.ToString()))
{
}
I get:
foreach (int __ in array)
{
var item = x.ToString();
}
Which (aside from the whitespace) is almost exactly what I want, except I can't figure out how to substitute the parameter x with __. In particular, this line doesn't seem to be working:
selectorBody = selectorBody.ReplaceNode(selectorParam, SyntaxFactory.IdentifierName("__"));
I'm trying to replace the Body of the SimpleLambdaExpression with its Parameter, which does nothing. I suspected that this wouldn't work, but how else can I substitute usages of x with __ in the lambda?
You are trying to replace the selectorParam node that is missing in the selectorBody, because in your case selectorBody is a call of the method (x.ToString() - InvocationExpressionSyntax). You can get the replacement by rewriting the code as follows:
var selectorBody = selectorExpr.Body as InvocationExpressionSyntax;
var nodeToReplace = (selectorBody.Expression as MemberAccessExpressionSyntax).Expression;
selectorBody = selectorBody.ReplaceNode(nodeToReplace, SyntaxFactory.IdentifierName("__"));
Or, if you want to rely on the parameters, then you should replace not SyntaxNode, but SyntaxTokens in the following way:
var selectorBody = selectorExpr.Body as ExpressionSyntax;
var selectorParam = selectorExpr.Parameter.Identifier;
IEnumerable<SyntaxToken> tokensToReplace = selectorBody.DescendantTokens()
.Where(token => String.Equals(token.Text, selectorParam.Text));
selectorBody = selectorBody.ReplaceTokens(tokensToReplace, (t1, t2) => SyntaxFactory.IdentifierName("__").Identifier);

Roslyn Rewrite Method Calls to use await Modifier

I a bunch of C# Files that I want to migrate to a new version of an internal framework we use for development. So far I have been able to accomplish certain things But there is a particular method invocation that i have to modify to use await modifier and the method calls come in different flavors like so
var a = EntityService.GetBy(Entities.INSTANT_ISSUANCE_REQUEST, requestFilter);
var b = EntityService.GetBy(Entities.ISSUANCE_SETTINGS, new DejaVuObject()).FirstOrDefault();
I have to modify the above method calls so that they use the following syntax
var a = await EntityService.GetBy(Entities.INSTANT_ISSUANCE_REQUEST, requestFilter);
var b = await EntityService.GetBy(Entities.ISSUANCE_SETTINGS, new DejaVuObject()).FirstOrDefault();
I am using Roslyn CSharpSyntaxWriter to traverse the syntax tree.
public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
{
var variableDeclarations =
from i in node.DescendantNodes().OfType<VariableDeclaratorSyntax>()
where i.DescendantNodes().OfType<EqualsValueClauseSyntax>().Any<EqualsValueClauseSyntax>(
p => p.DescendantNodes().OfType<InvocationExpressionSyntax>().Any<InvocationExpressionSyntax>())
select i;
foreach (var syntax in variableDeclarations)
{
var equalsToken = syntax.DescendantNodes();
//now we have the equals token
foreach (var syntaxNode in equalsToken)
{
if (syntaxNode is EqualsValueClauseSyntax)
{
var equalsValueSyntax = syntaxNode.DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocationExpressionSyntax in equalsValueSyntax)
{
var simpleMemberAcessExpressions = invocationExpressionSyntax
.DescendantNodes()
.OfType<IdentifierNameSyntax>()
.Where(i => i.Identifier.Text == "EntityService");
foreach (var simpleMemberAcessExpression in simpleMemberAcessExpressions)
{
var newExpression = $"{invocationExpressionSyntax.ToFullString()}";
Console.WriteLine(newExpression);
//TODO: Modify the Node
}
}
}
}
}
return base.VisitLocalDeclarationStatement(node);
}
My problem here is the output in Console is somewhat duplicative
EntityService.GetBy(Entities.ISSUANCE_SETTINGS, new DejaVuObject()).FirstOrDefault();
EntityService.GetBy(Entities.ISSUANCE_SETTINGS, new DejaVuObject())
Both refer to same InvocationExpression but because of the FirstOrDefault() it shows up twice in the console output
I would like for a way to filter the results so that they contain only unique Method Invocations and perform the actual modification of the method call by adding the await modifier and updating the node.
first of all, why override VisitLocalDeclarationStatement? Looks like you should be overriding VisitInvocationExpression.
You shouldn't be doing this:
var simpleMemberAcessExpressions =
invocationExpressionSyntax.DescendantNodes().OfType<IdentifierNameSyntax>()
Instead, it should be something like:
var identifier =
(node.Expression as MemberAccessExpressionSyntax)?.Expression as IdentifierNameSyntax;
if (identifier != null && identifier.Identifier.Text == "EntityService")
{
// process the invocation expression
}
Assuming the invocation expression is called node, your new expression is probably
var newExpression =
SyntaxFactory.ParenthesizedExpression(SyntaxFactory.AwaitExpression(node))
.WithAdditionalAnnotations(Simplifier.Annotation)
.WithAdditionalAnnotations(Formatter.Annotation);

How to get arguments of method to make completion with Roslyn?

I try to make code completion with Roslyn and AvalonEdit.
For example, user have code:
public void Completion(int i,int j) { }
And he types:
Completion(
So, i want to get arguments of method (int i, int j) and make code completion.
I write simple code, wich works with '.' and may be this code will work for '(' letter?
public List<ICompletionData> GetCompletionData(String code,int offset,CompletionType completionType)
{
var syntaxTree = SyntaxFactory.ParseSyntaxTree(code);
var compilation = CSharpCompilation.Create("foo")
.AddReferences(Mscorlib)
.AddSyntaxTrees(syntaxTree);
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var textSpan = GetTextSpan(offset,1);// '.' or '(' coordinates
ITypeSymbol lhsType = null;
if (completionType == CompletionType.DotCompletion)
{
var memberAccessNode = (MemberAccessExpressionSyntax)syntaxTree.GetRoot()
.DescendantNodes(textSpan).Last();
lhsType = semanticModel.GetTypeInfo(memberAccessNode.Expression).Type;
}
else if(completionType==CompletionType.ArgumentListCompletion)
{
var arr = syntaxTree.GetRoot().DescendantNodes(textSpan).Last();
var argumentListMode = (ArgumentListSyntax)syntaxTree.GetRoot().DescendantNodes(textSpan).Last();
var directive = argumentListMode.GetFirstDirective();
var arrgs=argumentListMode.Arguments;
//lhsType = semanticModel.GetTypeInfo(directive).Type;
//how to get lhsType?
}
if (lhsType == null)
return new List<ICompletionData>();
List<ICompletionData> completionDataColl = new List<ICompletionData>();
// and here i make completion data
foreach (var symbol in lhsType.GetMembers())
{
if (!symbol.CanBeReferencedByName
|| symbol.DeclaredAccessibility != Accessibility.Public
|| symbol.IsStatic)
continue;
}
}
The problem is, that i can not get ITypeSymbol lhsType. It is null.
How to get lhsType?
Or, maybe i should use another way?
I don't know the code completion (I couldn't find this class called CompletionType) itself, but here is a way based on Roslyn only: semantic model and the method invocation, which I believe you have available (make the method call string a InvocationExpressionSyntax)
To obtain the arguments of a method you can get its SymbolInfo from the semantic model. Then you get its symbol. A symbol contains the list of parameters(arguments).
You can call SemanticModel.GetSymbolInfo()
The result will provide you a symbol or candidate symbols (if it is an overloaded method).
A method symbol will provide the list of parameters, which is the arguments of that method.

Categories

Resources