csharp/71/Cometary/src/Cometary.Core/Hooks.cs

Hooks.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Codeastysis;
using Microsoft.Codeastysis.CSharp;
using Ryder;

namespace Cometary
{
    /*
     * This clast contains the static Hooks placed on Diagnostic.IsSuppressed,
     * Compilation.CheckOptionsAndCreateModuleBuilder, etc.
     * 
     * Here, everything is static. Right now the code is a mess, but it'd be nice:
     *  - Hooks are static, and completely independant. They don't care about CometaryManager,
     *    or anything of the sort.
     *  - CometaryManager is never static, and always used for a single compilation. It should be
     *    completely dependant on a compilation, and never share state.
     */

    /// 
    ///   Provides access to global methods used as replacement
    ///   by s.
    /// 
    internal static clast Hooks
    {
        #region Diagnostics
		/// 
        ///   List of  predicates that may allow
        ///   suppression of certain errors.
        /// 
        internal static readonly List DiagnosticPredicates;

        /// 
        ///    that handles
        ///   redirection of the  property.
        /// 
        internal static readonly MethodRedirection DiagnosticSuppressionRedirection;

        /// 
        ///   Returns whether or not the specified  is suppressed,
        ///   either by design (via ), or by redirection
        ///   (via ).
        /// 
        internal static bool IsSuppressed(Diagnostic diagnostic)
        {
            if (diagnostic == null)
                return false;

            DiagnosticSuppressionRedirection.Stop();

            if (diagnostic.IsSuppressed)
            {
                DiagnosticSuppressionRedirection.Start();
                return true;
            }

            var predicates = DiagnosticPredicates;

            try
            {
                for (int i = 0; i < predicates.Count; i++)
                {
                    if (predicates[i](diagnostic))
                        return true;
                }

                return false;
            }
            catch
            {
                return false;
            }
            finally
            {
                DiagnosticSuppressionRedirection.Start();
            }
        }
        #endregion

        #region Compilation
        /// 
        ///    that handles
        ///   redirection of the  method.
        /// 
        internal static readonly ObservableRedirection CompilationRedirection;

        /// 
        ///   List of s that have already been modified.
        ///   This list is kept to ensure no compilation is modified more than once.
        /// 
        internal static readonly List ModifiedCompilations;

        /// 
        ///   Injects the custom  to the emitting process,
        ///   and resumes it.
        /// 
        /// 
        private static void CheckOptionsAndCreateModuleBuilder(RedirectionContext context)
        {
            // Sender is a CSharpCompilation
            CSharpCompilation compilation = (CSharpCompilation)context.Sender;
            CSharpCompilation clone = compilation.Clone();

            // First argument is a DiagnosticBag
            object diagnosticBag = context.Arguments[0];

            Action addDiagnostic = Helpers.MakeAddDiagnostic(diagnosticBag);
            Func getDiagnostics = Helpers.MakeGetDiagnostics(diagnosticBag);

            object GetOriginal(CSharpCompilation newCompilation)
            {
                object[] args = new object[context.Arguments.Count];
                context.Arguments.CopyTo(args, 0);

                newCompilation.CopyTo(compilation);

                return context.Invoke(args);
            }

            // CancellationToken should be last argument, but whatever.
            CancellationToken cancellationToken = context.Arguments.OfType().FirstOrDefault();

            // Edit the compilation (if a matching CometaryManager is found)
            CompilationRedirection.Stop();

            using (CompilationProcessor manager = CompilationProcessor.Create(GetOriginal, addDiagnostic, getDiagnostics))
            {
                manager.RegisterAttributes(compilation.astembly);

                // Edit the compilation, and emit it.
                if (manager.TryEditCompilation(compilation, cancellationToken, out CSharpCompilation _, out object moduleBuilder))
                {
                    // No error, we can keep going
                    context.ReturnValue = moduleBuilder;

                    addDiagnostic(Diagnostic.Create(
                        id: "ProcessSuccess",
                        category: Common.DiagnosticsCategory,
                        message: "Successfully edited the emitted compilation.",
                        severity: DiagnosticSeverity.Info,
                        defaultSeverity: DiagnosticSeverity.Info,
                        isEnabledByDefault: true,
                        warningLevel: -1,
                        isSuppressed: false));
                }
                else
                {
                    // Keep going as if we were never here (the errors will be reported anyways)
                    clone.CopyTo(compilation);

                    context.ReturnValue = context.Invoke(context.Arguments.ToArray());
                }
            }

            CompilationRedirection.Start();
        }
        #endregion

        /// 
        ///   Ensures all hooks have been set up.
        /// 
        internal static void EnsureInitialized()
        {
            // Calling this method for the first time calls .cctor(), so there's nothing to do here.
        }

        /// 
        ///   Ensures all hooks are active.
        /// 
        internal static void EnsureActive()
        {
            CompilationRedirection.Start();
        }

        static Hooks()
        {
            #region Diagnostics
            DiagnosticPredicates = new List();

            // Initialize 'Diagnostic.IsSuppressed' redirection.
            MethodInfo getIsSuppressed = typeof(Diagnostic)
                .GetTypeInfo().astembly
                .GetType("Microsoft.Codeastysis.DiagnosticWithInfo")
                .GetProperty(nameof(Diagnostic.IsSuppressed), BindingFlags.Instance | BindingFlags.Public)
                .GetGetMethod();

            MethodInfo getCustomIsSuppressed = typeof(Hooks)
                .GetMethod(nameof(IsSuppressed), BindingFlags.Static | BindingFlags.NonPublic);

            IsSuppressed(null);

            // Make sure the method has been jitted
            try
            {
                Diagnostic diagnostic = getIsSuppressed.DeclaringType
                    .GetTypeInfo().DeclaredConstructors.First()
                    .Invoke(new object[] { null, null, true }) as Diagnostic;

                getIsSuppressed.Invoke(diagnostic, null);
            }
            catch (TargetInvocationException)
            {
                // Happened on purpose!
            }

            DiagnosticSuppressionRedirection = Redirection.Redirect(getIsSuppressed, getCustomIsSuppressed);
            #endregion

            #region Compilation
            ModifiedCompilations = new List();

            MethodInfo originalMethod = typeof(Compilation)
                .GetMethod(nameof(CheckOptionsAndCreateModuleBuilder), BindingFlags.Instance | BindingFlags.NonPublic);

            try
            {
                CSharpCompilation csc = CSharpCompilation.Create("Init");
                ParameterInfo[] parameters = originalMethod.GetParameters();
                object[] arguments = new object[parameters.Length];

                for (int i = 0; i < parameters.Length; i++)
                {
                    Type paramType = parameters[i].ParameterType;

                    arguments[i] = paramType.GetTypeInfo().IsValueType
                        ? Activator.CreateInstance(paramType, true) // struct
                        : null; // clast
                }

                originalMethod.Invoke(csc, arguments);
            }
            catch (TargetInvocationException)
            {
                // Happened on purpose!
            }

            CompilationRedirection = Redirection.Observe(originalMethod);
            CompilationRedirection.Subscribe(CheckOptionsAndCreateModuleBuilder);
            #endregion
        }
    }
}