I'm writing a system to process snippets written as unit tests for Noda Time, so I can include the snippets in the documentation. I've got a first pass working, but I wanted to tidy up the code. One of the things this needs to do when processing a snippet is work out which of the using
directives are actually required for that snippet. (There can be multiple snippets in a single source file, but each snippet will appear separately in the documentation - I don't want imports from one snippet affecting another.)
The working code deals with Document
instances - I create a separate Document
per snippet containing a single method and all the potential imports, add it to the project, and then remove unnecessary using
directives like this:
private async static Task<Document> RemoveUnusedImportsAsync(Document document)
{
var compilation = await document.Project.GetCompilationAsync();
var tree = await document.GetSyntaxTreeAsync();
var root = tree.GetRoot();
var unusedImportNodes = compilation.GetDiagnostics()
.Where(d => d.Id == "CS8019")
.Where(d => d.Location?.SourceTree == tree)
.Select(d => root.FindNode(d.Location.SourceSpan))
.ToList();
return document.WithSyntaxRoot(
root.RemoveNodes(unusedImportNodes, SyntaxRemoveOptions.KeepNoTrivia));
}
I've since learned that I could use the IOrganizeImportsService
when working with a document, but I'd like to just write it as a Script
, as that feels much cleaner in various ways.
Creating the script is easy, so I'd like to just analyze that for unused imports (after some earlier cleanup steps). Here's code I'd hoped would work for a script:
private static Script RemoveUnusedImports(Script script)
{
var compilation = script.GetCompilation();
var tree = compilation.SyntaxTrees.Single();
var root = tree.GetRoot();
var unusedImportNodes = compilation.GetDiagnostics()
.Where(d => d.Id == "CS8019")
.Where(d => d.Location?.SourceTree == tree)
.Select(d => root.FindNode(d.Location.SourceSpan))
.ToList();
var newRoot = root.RemoveNodes(unusedImportNodes, SyntaxRemoveOptions.KeepNoTrivia);
return CSharpScript.Create(newRoot.ToFullString(), script.Options);
}
Unfortunately, that doesn't find any diagnostics at all - they're just not produced in the compilation :(
Here's a short sample app demonstrating that:
using System;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
class Program
{
static void Main(string[] args)
{
string text = @"
using System;
using System.Collections.Generic;
Console.WriteLine(""I only need to use System"");";
Script script = CSharpScript.Create(text);
// Not sure whether this *should* be required, but it doesn't help...
script.Compile();
var compilation = script.GetCompilation();
foreach (var d in compilation.GetDiagnostics())
{
Console.WriteLine($"{d.Id}: {d.GetMessage()}");
}
}
}
Required package: Microsoft.CodeAnalysis.CSharp.Scripting (e.g. v2.1.0)
This produces no output :(
My guess is that this is intended, because scripting usually has different use cases. But is there any way of enabling more diagnostics for scripting purposes? Or is there some alternative way of detecting unused imports in a Script
? If not, I'll go back to my Document
-based approach - which would be a pity, as everything else seems to work quite nicely with scripts...
SyntaxNode.ReplaceNodes
appears to change the kind of the SyntaxTree options from "Script" to "Regular", so I have to tweak it back again after my replacements, but then it seems okay... – Maximilien