Use Roslyn MSBuildWorkspace Project AddAnalyzerReference doesn't load analyzers - c#

I'm working on a code report project.
Currently, I'm able to compile the solution projects, get the diagnostics related to the compilation, etc..
The problem appears when I try to load my custom IDiagnosticAnalyzers, I've tried to use the AnalyzerFileReference and the AnalyzerImageReference without any result, Always I access the projects.Analizers are empty.
var inmutableArray = (new List<IDiagnosticAnalyzer>
{
new VariableEndedWithIdNamedCorrectlyDiagnosticAnalyzer()
}).ToImmutableArray();
var analyzerImageReference = new AnalyzerImageReference(inmutableArray);
foreach (Project project in solution.Projects)
{
project.AddAnalyzerReference(analyzerImageReference );
//No analizers loaded....
}
UPDATE (thanks for the feedback [Josh Varty])
I've tried this two ways:
var newProjects = new List<Project>();
foreach (Project project in solution.Projects)
{
var newSolutionn= solution.AddAnalyzerReference(project.Id, analyzerImageReference);
newProjects.Add(newSolutionn.Projects.FirstOrDefault(p=> p.Id == project.Id));
}
foreach (Project project in solution.Projects)
{
var newProject = project.AddAnalyzerReference( analyzerImageReference);
}
In both cases have the analyzers loaded but when I get the compilation and I get the diagnostics, I don't get the output related to this analyzers (I think they are not being called at the get compilation function).
var compilation = newProject.GetCompilationAsync().Result;
var diagnostics = compilation.GetDiagnostics();
Any suggestions?

As I commented, most Roslyn objects are immutable. This means methods like AddAnalyzerReference() don't mutate the project, but instead return a new one.
I don't have an analyzer to test this, but I believe you can use the following. Note that I'm using Solution.AddAnalyzerReference() instead of the one you were using.
var inmutableArray =(new List<IDiagnosticAnalyzer>
{
new VariableEndedWithIdNamedCorrectlyDiagnosticAnalyzer()
}).ToImmutableArray();
var analyzerImageReference = new AnalyzerImageReference(inmutableArray);
Solution newSolution = solution;
//We iterate over the original solution
foreach (Project project in solution.Projects)
{
//But we save our work in the newSolution
newSolution = newSolution.AddAnalyzerReference(project.Id, analyzerImageReference);
}
//Now newSolution should contain all your changes.
//Maybe you want to save this reference?
solution = newSolution;

I've found the way to do it:
public static Task<ImmutableArray<Diagnostic>> GetDiagnosticsAsync(this Compilation compilation, ImmutableArray<DiagnosticAnalyzer> analyzers, AnalyzerOptions options, CancellationToken cancellationToken = default(CancellationToken))
{
options = options ?? new AnalyzerOptions(ImmutableArray<AdditionalStream>.Empty, ImmutableDictionary<string, string>.Empty);
Compilation newCompilation = null;
var analyzerDriver = AnalyzerDriver.Create(compilation, analyzers, options, out newCompilation, cancellationToken);
newCompilation.GetDiagnostics(cancellationToken);
return analyzerDriver.GetDiagnosticsAsync();
}
I've published a version of the open source project that I've been working using Roslyn, you can see the code and other thing related to analyzers and codefix.
https://bitbucket.org/jrierapeiro/codeanalyzer

I had similar question which i answered over here.
You have to use compilation.WithAnalyzer(analyzer) and then getDiagnostics()

Related

Using NuGet.PackageManager, How to get Installed packages with dependencies

Intro
Using NuGet.PackageManagement at runtime to enable users to install packages (from a private repo) where the packages will be loaded by System.Runtime.Loader.AssemblyLoadContext and executed.
The install method uses a NuGetPackageManager and NuGetProject that's been setup appropriately from what could understood in NuGet.Client
_nugetProject is an instance extending FolderNuGetProject that stores packages in a specified folder and GetInstalledPackagesAsync returns a list of installed packages instead of an empty list.
_packageManager is a NuGetPackageManager and _packageRepository is a SourceRepositoryProvider, both are default instances setup with the appropriate paths and source repos.
public async Task InstallPackageAsync(string packageId, SemanticVersion packageVersion, CancellationToken cancellationToken = default)
{
var packageIdentity = new PackageIdentity(packageId, new NuGetVersion(packageVersion.ToNormalizedString()));
using (var sourceCache = new SourceCacheContext()) {
var resolutionContext = new ResolutionContext(
DependencyBehavior.Highest,
includePrelease: true,
includeUnlisted: true,
VersionConstraints.None,
new GatherCache(),
sourceCache);
var projectContext = new EmptyNuGetProjectContext()
{
PackageExtractionContext = new PackageExtractionContext(
PackageSaveMode.Defaultv3,
XmlDocFileSaveMode.Skip,
ClientPolicyContext.GetClientPolicy(_nugetSettings, _nugetLogger),
_nugetLogger),
ActionType = NuGetActionType.Install,
};
var previewActions = await _packageManager.PreviewInstallPackageAsync(
_nugetProject,
packageIdentity,
resolutionContext,
projectContext,
_packageRepository.GetPrimaryRepositories().ToList(),
_packageRepository.GetRepositories().ToList(),
cancellationToken);
// List all actions in debug log.
var debugOutput = new StringBuilder();
debugOutput.AppendLine($"Install actions for {packageId}:");
foreach(var action in previewActions)
debugOutput.AppendLine($" - {action.NuGetProjectActionType}: {action.PackageIdentity}");
_logger.LogDebug(debugOutput.ToString());
await _packageManager.ExecuteNuGetProjectActionsAsync(
_nugetProject,
previewActions,
projectContext,
sourceCache,
cancellationToken);
}
}
Problem
The goal is to get a dependency graph of a specific package that was installed and use that information to load the assembly and it's required dependencies.
The difficulty is not being able to find any relevant API that already exists that would do this. Seeing as this is very common task in Visual Studio, it's been assumed that NuGet would provide a means to build a dependency graph.
There is await _nugetProject.GetInstalledPackagesAsync(cancellationToken) However this only returns a list of PackageReferences, that only contain the VersionRange and PackageId.
Note: NuGet.Client has served as the reference for how to use NuGet APIs properly

Roslyn MsBuildWorkspace compilation emit does not contain my changes

I'm trying to do minor changes to documents with roslyn and then compile the project to a new dll. But when I compile it to a new dll all my changes are gone. Am I missing something here?
var workspace = MSBuildWorkspace.Create();
var project = workspace.OpenProjectAsync(#"path\to\.csproj").Result;
var documents = project.DocumentIds;
foreach (var documentId in documents)
{
var document = project.GetDocument(documentId);
var root = document.GetSyntaxRootAsync().Result
var rewrite = new MyRewrite();
root = rewrite.Visit(root);
var newDocument = document.WithSyntaxRoot(root);
var compilation = newDocument.Project.GetCompilationAsync().Result;
// When I look at the sementatic model here it contains my changes.
var sementaticModel =
compilation.GetSemanticModel(newDocument.GetSyntaxTreeAsync().Result);
// But when I inspect this dll with dotPeek it's still the old code without changes.
compilation.Emit("new/dll/path");
}
Somehow it worked by changing:
var newDocument = document.WithSyntaxRoot(root);
to
var newDocument = document.WithText(root.GetText());

Visual Studio extension: Change the hint path of an assembly reference

I'm writing a Visual Studio extension and I would like to change the hint path of an assembly reference of a C#-project without to trigger the "File Modification Detected"-dialog.
<Reference Include="SomeAssembly">
<HintPath>C:\ChangeMe\SomeAssembly.dll</HintPath>
</Reference>
But in the VSLangProj110.Reference5-interface I can't find any property that I can use. (Accessed through VSLangProj140.VSProject3.References)
Microsoft.Build.BuildEngine.Project is outdated. Here is an updated working solution.
foreach (var dteProject in dte.Solution.Projects.OfType<Project>())
{
// You can edit the project through an object of Microsoft.Build.Evaluation.Project
var buildProject = ProjectCollection.GlobalProjectCollection.GetLoadedProjects(dteProject.FullName).First();
foreach (var item in buildProject.Items.Where(obj => obj.ItemType == "Reference"))
{
var newPath = SomeMethod(item.GetMetadata("HintPath"));
item.SetMetadataValue("HintPath", newPath);
}
// But you have to save through an object of EnvDTE.Project
dteProject.Save();
}
I create a demo and reproduce your issue on my side. I think it is a by design issue, if you modify the project outside the environment, it will popup the "File Modification Detected" dialog, we need to change it by manually.
you could post a feedback on the following link: https://connect.microsoft.com/VisualStudio/Feedback 
Update:
DTE2 dte = (DTE2)this.ServiceProvider.GetService(typeof(DTE));
EnvDTE.Project currentProject = dte.Solution.Projects.Item(1);
// Create a new Project object.
Microsoft.Build.BuildEngine.Project project = new Microsoft.Build.BuildEngine.Project();
project.Load(currentProject.FullName);
foreach (BuildItemGroup ig in project.ItemGroups)
{
//var items = ig.ToArray();
foreach (BuildItem item in ig.ToArray())
{
if (item.Include == "ClassLibrary1")
{
item.Include = "Utils";
item.SetMetadata("HintPath", #"C:\relativePath\Utils.dll");
}
}
}
project.Save(currentProject.FullName);

Get dependencies between classes in Roslyn

I am successfully getting dependencies between projects with Roslyn, and now I would like to get dependencies between classes, similar to the Code Map feature in Visual Studio Enterprise.
Here is my code, the "?????" part is where I imagine I could get something. I am very new to the Roslyn API, though, and I don't know how to proceed from there on.
Solution solution = MSBuildWorkspace.Create()
.OpenSolutionAsync(Path.Combine(repoRootFolder, "MySolution.sln"))
.Result;
ProjectDependencyGraph projdeps = solution.GetProjectDependencyGraph();
Digraph graph = new Digraph();
foreach (ProjectId projectId in projdeps.GetTopologicallySortedProjects())
{
string projName = solution.GetProject(projectId).Name;
var projDeps = projdeps.GetProjectsThatThisProjectDirectlyDependsOn(projectId);
foreach (ProjectId depId in projDeps)
{
Project dep = solution.GetProject(depId);
Compilation compilation = dep.GetCompilationAsync().Result;
foreach (var syntree in compilation.SyntaxTrees)
{
foreach (var classNode in syntree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>())
{
var classesThisClassNodeReferences = ?????????
}
}
string depName = dep.Name;
graph.Dependencies.Add(new Dependency
{
Source = projName,
Target = depName
});
}
}
I'm not sure about your requirements, but you can probably go for checking all descendant SyntaxNodes of the class and get the corresponding symbol, and it's type, and then collect these types. Something like the following:
var semantic = compilation.GetSemanticModel(syntree);
var typesForCurrentClass = classNode.DescendantNodes().Select(n =>
semantic.GetTypeInfo(n).Type);
Note that there can be multiple typesForCurrentClass for a given class symbol because of partial classes.

Add references to a project template programmatically

I'm doing a project Template. there is some custom parameters ( The services and attributes that he will be using during the implementation of the project ). Each service needs a specific reference. So depending on the custom parameters, I prepare a list that contains the paths of the needed assemblies. How can I add them to the project ?! I tried the following code but no result.`
var workspace = MSBuildWorkspace.Create();
var solution = workspace.OpenSolutionAsync(#"path").Result;
var projects = solution.Projects;
foreach (EnvDTE.Project proj in solution.Projects)
{
if (proj.Name == projectName)
{
VSLangProj.VSProject vsproj = (VSLangProj.VSProject)proj.Object;
foreach (string dll in Wizard.View.View.refs)
{
vsproj.References.Add(dll);
}
}
}`

Categories