programmatically extract interface using roslyn - c#

I am looking for a way to extract an interface from a document (c# class declaration) using Roslyn.
going from the reformatter example.
MSBuildWorkspace workspace = MSBuildWorkspace.Create();
// Open the solution within the workspace.
Solution originalSolution = workspace.OpenSolutionAsync(project).Result;
// Declare a variable to store the intermediate solution snapshot at each step.
MSBuildWorkspace workspace = MSBuildWorkspace.Create();
Solution originalSolution = workspace.OpenSolutionAsync(project).Result;
Solution newSolution = originalSolution;
foreach (ProjectId projectId in originalSolution.ProjectIds)
{
// Look up the snapshot for the original project in the latest forked solution.
Project proj = newSolution.GetProject(projectId);
var comp = proj.GetCompilationAsync().Result;
///var bind = comp.
if (proj.Name.EndsWith("Core.DataLayer"))
{
foreach (DocumentId documentId in proj.DocumentIds)
{
Document document = newSolution.GetDocument(documentId);
if (IsRepositoryDocument(document))
{
//How to implement this?
var newinterface = GetInterfaceFromRespository(document);
}
}
}
}
I started out using the sample "reformat solution" that the Roslyn team provided. However I am unable to find a public API to extract an interface from a given class file.
When trying to find this functionality in the Roslyn source code I can only find internal classes. I found the relevant classes in
"src\Features\Core\Portable\ExtractInterface" of the roslyn source code, i could copy these into my project and get it working, but i would rather not.
TLDR; is there a public API that I can use from C# to extract an interface from a class programatically?
Note that this is done in a "regular" C# project and not in a visual studio extension or analyzer.

You can get all the interfaces from a C# file using the below code statements.
string code = new StreamReader(filePath).ReadToEnd();
var syntaxTree = CSharpSyntaxTree.ParseText(code);
var syntaxRoot = syntaxTree.GetRoot();
IEnumerable<InterfaceDeclarationSyntax> interfaceDeclarations = syntaxRoot.DescendantNodes().OfType<InterfaceDeclarationSyntax>();
Then you can iterate the available interfaces in the file.

Related

Visual Studio SDK get type modifier information - is type abstract or internal?

I use the IVsObjectList2.GetCategoryField2 method to retrieve different information of a type.
Now I wonder how I can retrieve C# specific information like abstract or internal modifier of a type?
The Object Browser can displays this informations.
Update 1:
I have made another attempt to get this information. Via the DynamicTypeService and the IVsHierarchy (of the project) I can get the TypeResolutionService. This can then return the Type I'm are looking for, and form the Type I get the infomrations (internal, abstract, etc.)
Unfortunately, this only works with .NET Framework projects. .NET Core projects don't work. The assumption is that .NET core projects cause problems when resolving, because the VS add-in (or Visual Studio SDK) run under .NET Framework.
var dte = Package.GetGlobalService(typeof(DTE)) as DTE2;
var serviceProvider = new ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)dte);
IVsSimpleObjectList2 objectList;
....
objectList.CountSourceItems(index, out var vsHierarchy, out var itemid, out var pcItems);
DynamicTypeService dynamicTypeService = (DynamicTypeService)serviceProvider.GetService(typeof(DynamicTypeService));
var typeResolutionService = dynamicTypeService.GetTypeResolutionService(hier);
var type = typeResolutionService.GetType("ObjectBuilder.ObjectBrowserTestTypes.AbstractTestClass");
More Infos here: Visual Studio Extension get all classes and interfaces metadata
I'm still looking for a solution. Does anyone have another idea?
At the end, I decided not to retrieve the information about the types via the Object-Browser or IVsObjectManager2. The reason is that I didn't get all the information I needed.
For types in the currently loaded visual studio projects I'm using the ElementClass or CodeClass class.
var service = Package.GetGlobalService(typeof(DTE)) as DTE2;
Project project = service?.Solution?.Projects.Item(0);
CodeType codeType = project.CodeModel.CodeTypeFromFullName("Full name of Type");
if (codeType.Kind == vsCMElement.vsCMElementClass && codeType is CodeClass2 codeClass)
{
// get all the information form the code class
var typeDescription = new TypeDescription();
typeDescription.FullName = codeClass.FullName;
typeDescription.ContainsGenericParameters = codeClass.IsGeneric;
typeDescription.IsAbstract = codeClass.IsAbstract;
}
For types that are in a referenced assembly I'm using Mono.Cecil. The advantage of Mono.Cecil is, that it works with .NET Framework DLLs and .NET Core DLLs. The path of the referenced assembly can be gotten via the VS-SDK.
var vsProject = project.Object as VSLangProj.VSProject;
var assemblyPath = vsProject.References.Item(0).Path;
ModuleDefinition module = Mono.Cecil.ModuleDefinition.ReadModule(assemblyPath);
foreach (TypeDefinition type in module.Types)
{
var isAbstract = type.IsAbstract;
}

How do I retrieve text from the Visual Studio editor for use with Roslyn SyntaxTree?

I am attempting to write a Visual Studio extension that will analyze the C# code displayed in the editor and possibly update the code based on what I find. This would be on demand (via a menu item), and not using an analyzer and code fix.
There are a number of examples and samples on the Internet, but they all start either with the source code hard-coded in the samples, or create a new document, or look at each file in the VS solution that is open. How do I access the source code from the active editor window?
In a comment to my original question, #SJP gave a link to #Frank Bakker's answer to the question at Calling Roslyn from VSIX Command. This does work as outlined.
#JoshVarty provided a hint of the direction to go in his answer above. I combined that with code provided by #user1912383 for how to get an IWpfTextView answering the question Find an IVsTextView or IWpfTextView for a given ProjectItem, in 2010 RC extension. Here is the code I came up with:
var componentModel = (IComponentModel)Package.GetGlobalService(typeof(SComponentModel));
var textManager = (IVsTextManager)Package.GetGlobalService(typeof(SVsTextManager));
IVsTextView activeView = null;
ErrorHandler.ThrowOnFailure(textManager.GetActiveView(1, null, out activeView));
var editorAdapter = componentModel.GetService<IVsEditorAdaptersFactoryService>();
var textView = editorAdapter.GetWpfTextView(activeView);
var document = (textView.TextBuffer.ContentType.TypeName.Equals("CSharp"))
? textView : null;
In a comment after #user1912383's code mentioned above, #kman mentioned that this does not work for document types such as .sql files. It does, however, work for .cs files which is what I will be using it with.
First, you need to install the Microsoft.CodeAnalysis.EditorFeatures.Text package.
Then you need to add the appropriate using statement:
using Microsoft.CodeAnalysis.Text;
Now you can map between Visual Studio concepts (ITextSnapshot, ITextBuffer etc.) and Roslyn concepts (Document, SourceText etc.) with the extension methods found here: https://github.com/dotnet/roslyn/blob/master/src/EditorFeatures/Text/Extensions.cs
For example:
ITextSnapshot snapshot = ... //Get this from Visual Studio
var documents = snapshot.GetRelatedDocuments(); //There may be more than one

Dynamically compiled project losing resources

I need to compile source code of big project dynamically and output type can be Windows Application or Class Library.
Code is nicely executed and its possible to make .dll or .exe files, but problem is that, when I'm trying to make .exe file - it's losing resources like project icon. Result file doesn't include assembly information to.
Any way to solve this? (Expected result should be the same, that manual Build function on project file in Visual Studio 2015).
Thank you!
var workspace = MSBuildWorkspace.Create();
//Locating project file that is WindowsApplication
var project = workspace.OpenProjectAsync(#"C:\RoslynTestProjectExe\RoslynTestProjectExe.csproj").Result;
var metadataReferences = project.MetadataReferences;
// removing all references
foreach (var reference in metadataReferences)
{
project = project.RemoveMetadataReference(reference);
}
//getting new path of dlls location and adding them to project
var param = CreateParamString(); //my own function that returns list of references
foreach (var par in param)
{
project = project.AddMetadataReference(MetadataReference.CreateFromFile(par));
}
//compiling
var projectCompilation = project.GetCompilationAsync().Result;
using (var stream = new MemoryStream())
{
var result = projectCompilation.Emit(stream);
if (result.Success)
{
/// Getting result
//writing exe file
using (var file = File.Create(Path.Combine(_buildPath, fileName)))
{
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(file);
}
}
}
We never really designed the workspace API to include all the information you need to emit like this; in particular when you're calling Emit there's an EmitOptions you can pass that includes, amongst other things, resource information. But we don't expose that information since this scenario wasn't hugely considered. We've done some of the work in the past to enable this but ultimately never merged it. You might wish to consider filing a bug so we officially have the request somewhere.
So what can you do? I think there's a few options. You might consider not using Roslyn at all but rather modifying the project file and building that with the MSBuild APIs. Unfortunately I don't know what you're ultimately trying to achieve here (it would help if you mentioned it), but there's a lot more than just the compiler invocation that is involved in building a project. Changing references potentially changes other things too.
It'd also be possible, of course, to update MSBuildWorkspace yourself to pass this through. If you were to modify the Roslyn code, you'll see we implement a series of interfaces named "ICscHostObject#" (where # is a number) and we get passed the information from MSBuild to that. It looks like we already stash that in the command line arguments, so you might be able to pass that to our command line parser and get the data back you need that way.

C# Roslyn API, Reading a .cs file, updating a class, writing back to .cs file

I have this working code that will load a .cs file into the Roslyn SyntaxTree class, create a new PropertyDeclarationSyntax, insert it into the class, and re-write the .cs file. I'm doing this as a learning experience as well as some potential future ideas. I found that there doesn't really seem to be a full Roslyn API documentation anywhere and I'm unsure if I am doing this efficiently. My main concern is where I call 'root.ToFullString()' - whilst it works, is this the right way to do it?
using System.IO;
using System.Linq;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
class RoslynWrite
{
public RoslynWrite()
{
const string csFile = "MyClass.cs";
// Parse .cs file using Roslyn SyntaxTree
var syntaxTree = SyntaxTree.ParseFile(csFile);
var root = syntaxTree.GetRoot();
// Get the first class from the syntax tree
var myClass = root.DescendantNodes().OfType<ClassDeclarationSyntax>().First();
// Create a new property : 'public bool MyProperty { get; set; }'
var myProperty = Syntax.PropertyDeclaration(Syntax.ParseTypeName("bool"), "MyProperty")
.WithModifiers(Syntax.Token(SyntaxKind.PublicKeyword))
.WithAccessorList(
Syntax.AccessorList(Syntax.List(
Syntax.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithSemicolonToken(Syntax.Token(SyntaxKind.SemicolonToken)),
Syntax.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
.WithSemicolonToken(Syntax.Token(SyntaxKind.SemicolonToken)))));
// Add the new property to the class
var updatedClass = myClass.AddMembers(myProperty);
// Update the SyntaxTree and normalize whitespace
var updatedRoot = root.ReplaceNode(myClass, updatedClass).NormalizeWhitespace();
// Is this the way to write the syntax tree? ToFullString?
File.WriteAllText(csFile, updatedRoot.ToFullString());
}
}
Answered on the Roslyn CTP forum in this post:
That approach is generally fine, though if you are worried about allocating a string for the text of the entire file, you should probably use IText.Write(TextWriter) instead of ToFullString().
Keep in mind that it's possible to generate trees that will not round-trip through the parser. For example, if you generated something that violates precedence rules, the SyntaxTree construction APIs won't catch that.

Roslyn Add a document to a project

I'm running roslyn ctp2
I am attempting to add a new html file to a project
IWorkspace workspace = Workspace.LoadSolution("MySolution.sln");
var originalSolution = workspace.CurrentSolution;
ISolution newSolution = originalSolution;
newSolution.GetProject(newSolution.ProjectIds.First())
.AddDocument("index.html", "<html></html>");
workspace.ApplyChanges(originalSolution, newSolution);
This results in no changes being written. I am trying to get the new html file to appear in VS
There are two issues here:
Roslyn ISolution, IProject, and IDocument objects are immutable, so in order to see changes you would need to create a new ISolution with the changes, then call Workspace.ApplyChanges().
In Roslyn, IDocument objects are only created for files that are passed to the compiler. Another way of saying this is things that are part of the Compile ItemGroup in the project file. For other files (including html files), you should use the normal Visual Studio interfaces like IVsSolution.
Workspaces are immutable. That means that any method that sounds like it's going to modify the workspace will instead be returning a new instance with the changes applied.
So you want something like:
IWorkspace workspace = Workspace.LoadSolution("MySolution.sln");
var originalSolution = workspace.CurrentSolution;
var project = originalSolution.GetProject(originalSolution.ProjectIds.First());
IDocument doc = project.AddDocument("index.html", "<html></html>");
workspace.ApplyChanges(originalSolution, doc.Project.Solution);
However, I'm not near a machine with Roslyn installed at the moment, so I can't guarantee this 100%.

Categories