| 1 | using System.Text; |
| 2 | using System.Xml.Linq; |
| 3 | |
| 4 | namespace CascadeIDE.Services; |
| 5 | |
| 6 | /// <summary>Reads <c>ImplicitUsings</c>, <c>Using Include/Remove</c> from a <c>.csproj</c>.</summary> |
| 7 | public sealed record CSharpProjectUsingsProfile( |
| 8 | string ProjectPath, |
| 9 | string? SdkAttribute, |
| 10 | bool ImplicitUsingsEnabled, |
| 11 | IReadOnlyList<string> UsingIncludes, |
| 12 | IReadOnlyList<string> UsingRemoves) |
| 13 | { |
| 14 | public static CSharpProjectUsingsProfile? TryLoad(string? projectPath) |
| 15 | { |
| 16 | if (string.IsNullOrWhiteSpace(projectPath) || !File.Exists(projectPath)) |
| 17 | return null; |
| 18 | |
| 19 | try |
| 20 | { |
| 21 | var doc = XDocument.Load(projectPath); |
| 22 | var root = doc.Root; |
| 23 | if (root is null) |
| 24 | return null; |
| 25 | |
| 26 | var sdk = root.Attribute("Sdk")?.Value; |
| 27 | var implicitUsings = ReadImplicitUsings(root); |
| 28 | var includes = ReadUsingItems(root, "Include"); |
| 29 | var removes = ReadUsingItems(root, "Remove"); |
| 30 | return new CSharpProjectUsingsProfile( |
| 31 | projectPath, |
| 32 | sdk, |
| 33 | implicitUsings, |
| 34 | includes, |
| 35 | removes); |
| 36 | } |
| 37 | catch |
| 38 | { |
| 39 | return null; |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | public IReadOnlyList<string> ResolveNamespaces() |
| 44 | { |
| 45 | var set = new HashSet<string>(StringComparer.Ordinal); |
| 46 | foreach (var ns in CSharpSdkImplicitUsingsCatalog.ForProjectSdk(SdkAttribute, ImplicitUsingsEnabled)) |
| 47 | set.Add(ns); |
| 48 | foreach (var ns in UsingIncludes) |
| 49 | set.Add(ns); |
| 50 | foreach (var ns in UsingRemoves) |
| 51 | set.Remove(ns); |
| 52 | return set.OrderBy(static n => n, StringComparer.Ordinal).ToList(); |
| 53 | } |
| 54 | |
| 55 | private static bool ReadImplicitUsings(XElement root) |
| 56 | { |
| 57 | var value = ReadProperty(root, "ImplicitUsings"); |
| 58 | if (string.IsNullOrWhiteSpace(value)) |
| 59 | return SdkPresent(root); |
| 60 | return value.Equals("enable", StringComparison.OrdinalIgnoreCase) |
| 61 | || value.Equals("true", StringComparison.OrdinalIgnoreCase); |
| 62 | } |
| 63 | |
| 64 | private static bool SdkPresent(XElement root) => |
| 65 | !string.IsNullOrWhiteSpace(root.Attribute("Sdk")?.Value); |
| 66 | |
| 67 | private static string? ReadProperty(XElement root, string name) |
| 68 | { |
| 69 | foreach (var pg in root.Elements("PropertyGroup")) |
| 70 | { |
| 71 | var el = pg.Element(name); |
| 72 | if (el is not null) |
| 73 | return el.Value.Trim(); |
| 74 | } |
| 75 | |
| 76 | return null; |
| 77 | } |
| 78 | |
| 79 | private static List<string> ReadUsingItems(XElement root, string attributeName) |
| 80 | { |
| 81 | var list = new List<string>(); |
| 82 | foreach (var item in root.Elements("ItemGroup").SelectMany(g => g.Elements("Using"))) |
| 83 | { |
| 84 | var value = item.Attribute(attributeName)?.Value?.Trim(); |
| 85 | if (!string.IsNullOrWhiteSpace(value)) |
| 86 | list.Add(value); |
| 87 | } |
| 88 | |
| 89 | return list; |
| 90 | } |
| 91 | } |
| 92 | |