Backend/ReSharperPlugin/ForTea.ReSharperPlugin/Psi/Resolve/Macros/T4MacroResolutionCache.cs (94 lines of code) (raw):

using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using GammaJul.ForTea.Core.Psi.Cache; using GammaJul.ForTea.Core.Tree; using JetBrains.Annotations; using JetBrains.Application.Parts; using JetBrains.Application.Threading; using JetBrains.DataFlow; using JetBrains.Diagnostics; using JetBrains.Interop.WinApi; using JetBrains.Lifetimes; using JetBrains.ProjectModel; using JetBrains.ReSharper.Psi; using JetBrains.ReSharper.Psi.Caches; using JetBrains.Util; using JetBrains.VsIntegration.ProjectModel; using Microsoft.VisualStudio.Shell.Interop; namespace JetBrains.ForTea.ReSharperPlugin.Psi.Resolve.Macros { /// <summary> /// In R#, it is only possible to resolve macros on the main thread, /// so we have to cache them to be able to access them from the daemon /// </summary> [PsiComponent(InstantiationEx.LegacyDefault)] public sealed class T4MacroResolutionCache : T4PsiAwareCacheBase<T4MacroResolutionRequest, T4MacroResolutionData> { [NotNull] public Signal<IPsiSourceFile> OnFileMarkedForInvalidation { get; } public T4MacroResolutionCache( Lifetime lifetime, [NotNull] IShellLocks locks, [NotNull] IPersistentIndexManager persistentIndexManager, [NotNull] ISolution solution ) : base(lifetime, locks, persistentIndexManager, T4MacroResolutionDataMarshaller.Instance) => OnFileMarkedForInvalidation = new(lifetime, "Update from T4MacroResolutionCache"); [NotNull] protected override T4MacroResolutionRequest Build(IT4File file) { var macros = file .GetThisAndChildrenOfType<IT4Macro>() .Where(macro => macro.RawAttributeValue != null) .Distinct(macro => macro.RawAttributeValue.GetText()); return new T4MacroResolutionRequest(macros.ToList()); } public override void Merge(IPsiSourceFile sourceFile, object builtPart) { var request = (T4MacroResolutionRequest)builtPart.NotNull(); var projectFile = sourceFile.ToProjectFile().NotNull(); var macroNames = request.MacrosToResolve.Select(macro => macro.RawAttributeValue.GetText()); var oldKeys = Map.TryGetValue(sourceFile)?.ResolvedMacros.Keys ?? EmptyList<string>.Instance; var data = new T4MacroResolutionData(ResolveHeavyMacros(macroNames, projectFile)); var newKeys = data.ResolvedMacros.Keys; base.Merge(sourceFile, data); if (!newKeys.All(it => oldKeys.Contains(it))) { OnFileMarkedForInvalidation.Fire(sourceFile); } } [CanBeNull] public T4MacroResolutionData TryGetValue(IPsiSourceFile sourceFile) { base.TryGetValue(sourceFile, out var value); return value; } [NotNull] private IReadOnlyDictionary<string, string> ResolveHeavyMacros( [NotNull] IEnumerable<string> macros, [NotNull] IProjectFile file) { var result = new Dictionary<string, string>(); Lazy<IVsBuildMacroInfo> vsBuildMacroInfo = Lazy.Of(() => TryGetVsBuildMacroInfo(file), false); foreach (string macro in macros) { bool succeeded = false; string value = null; if (vsBuildMacroInfo.Value != null) { succeeded = HResultHelpers.SUCCEEDED(vsBuildMacroInfo.Value.GetBuildMacroValue(macro, out value)) && !string.IsNullOrEmpty(value); } if (!succeeded) { value = MSBuildExtensions.GetStringValue(T4ResolutionUtils.TryGetVsHierarchy(file), macro, null); succeeded = !string.IsNullOrEmpty(value); } if (succeeded) { result[macro] = value; } } return result; } /// <summary> /// The <see cref="IVsHierarchy"/> representing the project file /// normally implements <see cref="IVsBuildMacroInfo"/>. /// </summary> /// <returns>An instance of <see cref="IVsBuildMacroInfo"/> if found.</returns> [CanBeNull, SuppressMessage("ReSharper", "SuspiciousTypeConversion.Global")] private static IVsBuildMacroInfo TryGetVsBuildMacroInfo([NotNull] IProjectFile file) => T4ResolutionUtils.TryGetVsHierarchy(file) as IVsBuildMacroInfo; } }