File: ProjectModel.cs
Web Access
Project: src\src\Tools\SemanticSearch\Extensions\Microsoft.CodeAnalysis.SemanticSearch.Extensions.csproj (Microsoft.CodeAnalysis.SemanticSearch.Extensions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#if NET
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml;
using System.Text;
 
namespace Microsoft.CodeAnalysis.SemanticSearch.Extensions;
 
/// <summary>
/// Models project information not tracked by Compilation.
/// </summary>
public sealed class ProjectModel
{
    private readonly Lazy<ImmutableDictionary<string, ResxFile>> _lazyResxFiles;
 
    public string FilePath { get; }
 
    internal ProjectModel(string filePath)
    {
        FilePath = filePath;
        _lazyResxFiles = new(LoadResxFiles, isThreadSafe: true);
    }
 
    internal ProjectModel(string filePath, ImmutableDictionary<string, ResxFile> resxFiles)
    {
        FilePath = filePath;
        _lazyResxFiles = new(() => resxFiles, isThreadSafe: true);
    }
 
    public ImmutableDictionary<string, ResxFile> ResxFiles
        => _lazyResxFiles.Value;
 
    public ProjectModel ReplaceResxFile(ResxFile file)
        => new(FilePath, ResxFiles.SetItem(file.FilePath, file));
 
    internal ImmutableDictionary<string, ResxFile> LoadResxFiles()
    {
        var resxFiles = ImmutableDictionary.CreateBuilder<string, ResxFile>();
        var projectDirectory = Path.GetDirectoryName(FilePath)!;
 
        // TODO: get EmbeddedResources items from msbuild instead
        foreach (var filePath in Directory.EnumerateFileSystemEntries(projectDirectory, "*.resx", SearchOption.AllDirectories))
        {
            resxFiles.Add(filePath, ResxFile.ReadFromFile(filePath));
        }
 
        return resxFiles.ToImmutable();
    }
 
    internal static IEnumerable<(string filePath, string? newContent)> GetChanges(ProjectModel oldModel, ProjectModel newModel)
    {
        if (!oldModel._lazyResxFiles.IsValueCreated && !newModel._lazyResxFiles.IsValueCreated)
        {
            yield break;
        }
 
        foreach (var (filePath, newResx) in newModel.ResxFiles)
        {
            var newContent = newResx.GetContent();
 
            if (oldModel.ResxFiles.TryGetValue(filePath, out var oldResx) && newContent == oldResx.GetContent())
            {
                continue;
            }
 
            // new or updated resx file:
            yield return (filePath, newContent);
        }
 
        foreach (var (filePath, _) in oldModel.ResxFiles)
        {
            if (!newModel.ResxFiles.ContainsKey(filePath))
            {
                // deleted resx file:
                yield return (filePath, null);
            }
        }
    }
}
 
public sealed class ResxFile
{
    public string FilePath { get; }
 
    private readonly ImmutableDictionary<string, string> _changes;
 
    internal ResxFile(string filePath, ImmutableDictionary<string, string> changes)
    {
        FilePath = filePath;
        _changes = changes;
    }
 
    internal static ResxFile ReadFromFile(string filePath)
    {
        return new ResxFile(filePath, changes: ImmutableDictionary<string, string>.Empty);
    }
 
    public ResxFile AddString(string name, string value)
        => new(FilePath, _changes.SetItem(name, value));
 
    internal string GetContent()
    {
        if (_changes.Count == 0)
        {
            return File.ReadAllText(FilePath, Encoding.UTF8);
        }
 
        var newDocument = XDocument.Load(FilePath, LoadOptions.None);
 
        foreach (var (name, value) in _changes)
        {
            newDocument.Root!.Add(new XElement("data",
                new XAttribute("name", name),
                new XAttribute(XNamespace.Xml + "space", "preserve"),
                new XElement("value", value)
            ));
        }
 
        using var stream = new MemoryStream();
 
        using var xmlWriter = XmlWriter.Create(stream, new()
        {
            Indent = true,
            Encoding = Encoding.UTF8,
            IndentChars = "\t",
            NewLineChars = "\r\n",
            NewLineOnAttributes = false,
        });
 
        newDocument.Save(xmlWriter);
        xmlWriter.Close();
 
        stream.Position = 0;
        using var reader = new StreamReader(stream, Encoding.UTF8);
        return reader.ReadToEnd();
    }
}
#endif