Skip to content

Instantly share code, notes, and snippets.

@flq
Last active March 8, 2019 22:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flq/da60c3f428f4d11159c0 to your computer and use it in GitHub Desktop.
Save flq/da60c3f428f4d11159c0 to your computer and use it in GitHub Desktop.
Roslyn Refactoring

Required Nugets should be covered by pulling in:

  • Microsoft.CodeAnalysis.CSharp.WorkSpaces for all Roslyn-related things
  • Mono.Options for CLI arguments parsing
  • Rx.Main for Observable goodness.

Your machine will also need Build Tools for latest Visual Studio bits, which you can download. The SO answer http://stackoverflow.com/a/25512566/51428 should have you covered.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;
namespace ReFuc
{
internal class CompileHub
{
private readonly Log _log;
private readonly MSBuildWorkspace _ws;
private Project _project;
private Solution _solution;
public CompileHub(Log log)
{
_log = log;
_ws = MSBuildWorkspace.Create();
}
public async Task StartWithSolution(string sln)
{
_log.LogInfo("Creating Workspace from solution {0}", Path.GetFileName(sln));
_solution = await _ws.OpenSolutionAsync(sln);
_log.LogInfo("WorkSpace created");
}
public async Task StartWithProject(string proj)
{
_log.LogInfo("Creating Workspace from project {0}", Path.GetFileName(proj));
using (_log.StartWaiting())
{
_project = await _ws.OpenProjectAsync(proj);
}
_log.LogInfo("WorkSpace created");
}
public IObservable<Compilation> WithProjects(Func<Project, bool> selector = null)
{
return Observable.Create<Compilation>(async obs =>
{
foreach (var p in EnumerateContainedProjects(selector))
{
_log.LogInfo("About to compile Project {0}...", p.Name);
using (_log.StartWaiting())
{
var compilation = await p.GetCompilationAsync();
obs.OnNext(compilation);
}
}
obs.OnCompleted();
});
}
private IEnumerable<Project> EnumerateContainedProjects(Func<Project, bool> selector = null)
{
return (_solution != null ? _solution.Projects : new[] {_project}).Where(selector ?? (p => true));
}
}
}
using System;
using Microsoft.CodeAnalysis;
namespace ReFuc.Tasks
{
public interface IRefactoringTask
{
string Description { get; }
IObservable<SyntaxTree> Run(IObservable<Compilation> compilations);
}
}
using System;
using System.Linq;
using System.Reactive.Linq;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace ReFuc
{
public static class ObservableSyntaxArtefacts
{
public static IObservable<ClassDeclarationSyntax> WithClasses(
this IObservable<Compilation> compilations,
Func<ClassDeclarationSyntax,bool> selector = null)
{
return compilations
.WithSyntaxTrees()
.SelectMany(st => st.GetRoot()
.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.Where(selector ?? (cds => true)));
}
public static IObservable<T> ScanFor<T>(
this IObservable<ClassDeclarationSyntax> classes,
Func<IObservable<SyntaxNode>, IObservable<SyntaxNode>> selector)
where T : SyntaxNode
{
return selector(classes.SelectMany(cds => cds.DescendantNodes()))
.Select(a => a.GetParentChain().OfType<T>().FirstAsync())
.SelectMany(_ => _);
}
[PublicAPI]
public static IObservable<SyntaxTree> WithSyntaxTrees(this IObservable<Compilation> compilations)
{
return compilations.SelectMany(c => c.SyntaxTrees);
}
[PublicAPI]
public static IObservable<MethodDeclarationSyntax> WithMethods(this IObservable<ClassDeclarationSyntax> classes,
Func<MethodDeclarationSyntax, bool> selector = null)
{
return classes.SelectMany(c => c.DescendantNodes()
.OfType<MethodDeclarationSyntax>().Where(selector ?? (mds => true)));
}
[PublicAPI]
public static IObservable<SyntaxNode> GetParentChain(this SyntaxNode node)
{
return Observable.Generate(node, n => n.Parent != null, n => n.Parent, n => n);
}
}
}
using System;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ReFuc.Tasks;
using ReFuc.Utilities;
namespace ReFuc
{
[UsedImplicitly]
class Program
{
private static readonly Log Log = new Log();
static void Main(string[] args)
{
string solutionPath = null;
string projectPath = null;
string taskName = null;
var os = new OptionSet
{
{"solution=|sln=", "solution entry point", path => solutionPath = path},
{"project=|prj=", "project entry point", path => projectPath = path},
{"task=|t=", "task to run", tsk => taskName = tsk}
};
os.Parse(args);
if (taskName == null)
Exit("No taskname provided");
// ReSharper disable once AssignNullToNotNullAttribute
var taskType = FindType(taskName);
if (taskType == null)
Exit("No type called " + taskName + " found");
// ReSharper disable once AssignNullToNotNullAttribute
var taskInstance = (IRefactoringTask) Activator.CreateInstance(taskType);
var hub = new CompileHub(Log);
if (solutionPath != null)
hub.StartWithSolution(solutionPath).Wait();
else if (projectPath != null)
hub.StartWithProject(projectPath).Wait();
Log.LogInfo("About to run {0}", taskInstance.Description);
taskInstance.Run(hub.WithProjects()).Subscribe(SaveTree, Done);
Console.ReadLine();
}
private static void Exit(string msg)
{
Log.LogWarning(msg);
Console.ReadLine();
Environment.Exit(0);
}
private static void Done()
{
Log.LogInfo("Done");
}
private static void SaveTree(SyntaxTree syntaxTree)
{
try
{
using (var fs = new FileStream(syntaxTree.FilePath, FileMode.Truncate))
using (var tw = new StreamWriter(fs))
{
syntaxTree.GetText().Write(tw);
}
}
catch (Exception e)
{
Log.LogException(e);
}
}
private static Type FindType(string taskName)
{
return typeof (Program).Assembly.GetExportedTypes().FirstOrDefault(t => t.Name == taskName);
}
}
}
using System;
using System.Reactive.Linq;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace ReFuc.Tasks
{
[UsedImplicitly] //Through reflection when running tasks
public class RemoveLogEntryCode : IRefactoringTask
{
public string Description { get { return "Remove the trace log entries"; } }
public IObservable<SyntaxTree> Run(IObservable<Compilation> compilations)
{
return compilations.WithClasses()
.ScanFor<MethodDeclarationSyntax>(query =>
query.OfType<LocalDeclarationStatementSyntax>()
.Where(n => n.ToString().Contains("logger.Enter")))
.Where(method => method.Identifier.ToString().Contains("LoadLimits"))
.Select(node => node.SyntaxTree)
.Distinct(st => st.FilePath)
.Select(st =>
{
var v = new LogTraceFileRemover();
var newRoot = (CSharpSyntaxNode) v.Visit(st.GetRoot());
return CSharpSyntaxTree.Create(newRoot, path: st.FilePath);
});
}
private class LogTraceFileRemover : CSharpSyntaxRewriter
{
public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
{
if (node.ToString().Contains("logger.Enter"))
{
return null;
}
return base.VisitLocalDeclarationStatement(node);
}
public override SyntaxNode VisitExpressionStatement(ExpressionStatementSyntax node)
{
if (node.ToString().Contains("logger.Exit"))
{
return null;
}
return base.VisitExpressionStatement(node);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment