| File: Commands\Reference\FileBasedAppReferenceEditor.cs | Web Access |
| Project: src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Build.Construction; using Microsoft.Build.Execution; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Commands.Reference; internal sealed class FileBasedAppReferenceEditor { private readonly FileBasedAppSourceEditor _sourceEditor; private readonly ProjectInstance _projectInstance; public FileBasedAppReferenceEditor(string entryPointFilePath) { EntryPointFilePath = Path.GetFullPath(entryPointFilePath); EntryPointFileDirectory = Path.GetDirectoryName(EntryPointFilePath) ?? throw new InvalidOperationException($"Source file path '{EntryPointFilePath}' does not have a containing directory."); _sourceEditor = FileBasedAppSourceEditor.Load(SourceFile.Load(EntryPointFilePath)); _projectInstance = CreateExpansionProjectInstance(EntryPointFilePath); } public string EntryPointFilePath { get; } public string EntryPointFileDirectory { get; } public IEnumerable<string> GetReferencesForDisplay() { foreach (var directive in _sourceEditor.Directives) { if (directive is CSharpDirective.Project projectDirective) { yield return projectDirective.Name; } else if (directive is CSharpDirective.Ref refDirective) { yield return refDirective.Name; } } } public int AddProjectReferences(IEnumerable<(string ProjectFilePath, string DirectiveInclude)> references) { int numberOfAddedReferences = 0; foreach (var reference in references) { if (TryGetProjectReferenceDisplay(reference.ProjectFilePath, out var existingReferenceDisplay)) { Reporter.Output.WriteLine(string.Format( CliStrings.ProjectAlreadyHasAreference, existingReferenceDisplay)); continue; } numberOfAddedReferences++; _sourceEditor.Add(new CSharpDirective.Project(default, reference.DirectiveInclude)); Reporter.Output.WriteLine(string.Format(CliStrings.ReferenceAddedToTheProject, reference.DirectiveInclude)); } return numberOfAddedReferences; } public int AddFileBasedAppReferences(IEnumerable<(string FilePath, string DirectiveInclude)> references) { int numberOfAddedReferences = 0; foreach (var reference in references) { if (TryGetFileBasedAppReferenceDisplay(reference.FilePath, out var existingReferenceDisplay)) { Reporter.Output.WriteLine(string.Format( CliStrings.ProjectAlreadyHasAreference, existingReferenceDisplay)); continue; } numberOfAddedReferences++; _sourceEditor.Add(new CSharpDirective.Ref(default, reference.DirectiveInclude)); Reporter.Output.WriteLine(string.Format(CliStrings.ReferenceAddedToTheProject, reference.DirectiveInclude)); } return numberOfAddedReferences; } public int RemoveReferences(IEnumerable<string> references) { int totalNumberOfRemovedReferences = 0; foreach (var reference in references) { int numberOfRemovedReferences = RemoveFileBasedAppReferences(reference); if (numberOfRemovedReferences == 0) { numberOfRemovedReferences = RemoveProjectReferences(reference); } if (numberOfRemovedReferences == 0) { Reporter.Output.WriteLine(string.Format( CliStrings.ProjectReferenceCouldNotBeFound, reference)); } totalNumberOfRemovedReferences += numberOfRemovedReferences; } return totalNumberOfRemovedReferences; } public void Save() => _sourceEditor.SourceFile.Save(); private int RemoveProjectReferences(string reference) { var referencePaths = GetProjectReferenceArgumentFullPathAlternatives(reference).ToList(); var matchingDirectives = _sourceEditor.Directives .OfType<CSharpDirective.Project>() .Where(d => referencePaths.Any(p => PathsEqual(GetProjectDirectiveFullPath(d), p))) .OrderByDescending(d => d.Info.Span.Start) .ToList(); foreach (var directive in matchingDirectives) { _sourceEditor.Remove(directive); Reporter.Output.WriteLine(string.Format(CliStrings.ProjectReferenceRemoved, directive.Name)); } return matchingDirectives.Count; } private int RemoveFileBasedAppReferences(string reference) { var referencePath = Path.GetFullPath(reference); var matchingDirectives = _sourceEditor.Directives .OfType<CSharpDirective.Ref>() .Where(d => PathsEqual(GetRefDirectiveFullPath(d), referencePath)) .OrderByDescending(d => d.Info.Span.Start) .ToList(); foreach (var directive in matchingDirectives) { _sourceEditor.Remove(directive); Reporter.Output.WriteLine(string.Format(CliStrings.ProjectReferenceRemoved, directive.Name)); } return matchingDirectives.Count; } private bool TryGetProjectReferenceDisplay(string projectFilePath, out string display) { var directive = _sourceEditor.Directives .OfType<CSharpDirective.Project>() .FirstOrDefault(d => PathsEqual(GetProjectDirectiveFullPath(d), projectFilePath)); if (directive is null) { display = string.Empty; return false; } display = directive.Name; return true; } private bool TryGetFileBasedAppReferenceDisplay(string filePath, out string display) { var directive = _sourceEditor.Directives .OfType<CSharpDirective.Ref>() .FirstOrDefault(d => PathsEqual(GetRefDirectiveFullPath(d), filePath)); if (directive is null) { display = string.Empty; return false; } display = directive.Name; return true; } private string GetProjectDirectiveFullPath(CSharpDirective.Project projectDirective) { var expandedDirective = projectDirective.WithName( _projectInstance.ExpandString(projectDirective.Name), CSharpDirective.Project.NameKind.Expanded); var resolvedDirective = expandedDirective.EnsureProjectFilePath(ErrorReporters.IgnoringReporter); return GetFullPathRelativeToEntryPointFileDirectory(resolvedDirective.Name); } private string GetRefDirectiveFullPath(CSharpDirective.Ref refDirective) { var expandedDirective = refDirective.WithName( _projectInstance.ExpandString(refDirective.Name), CSharpDirective.Ref.NameKind.Expanded); return expandedDirective.EnsureResolvedPath(ErrorReporters.IgnoringReporter).Name; } private IEnumerable<string> GetProjectReferenceArgumentFullPathAlternatives(string reference) { yield return ResolveProjectReferenceArgumentFullPath(Path.GetFullPath(reference)); var appRelativePath = Path.GetFullPath(Path.Combine(EntryPointFileDirectory, reference.Replace('\\', '/'))); if (!PathsEqual(appRelativePath, reference)) { yield return ResolveProjectReferenceArgumentFullPath(appRelativePath); } } private static string ResolveProjectReferenceArgumentFullPath(string fullPath) => Directory.Exists(fullPath) ? MsbuildProject.GetProjectFileFromDirectory(fullPath) : fullPath; private string GetFullPathRelativeToEntryPointFileDirectory(string path) => Path.GetFullPath(Path.Combine(EntryPointFileDirectory, path.Replace('\\', '/'))); private static bool PathsEqual(string left, string right) => string.Equals( NormalizePath(Path.GetFullPath(left)), NormalizePath(Path.GetFullPath(right)), StringComparison.OrdinalIgnoreCase); private static ProjectInstance CreateExpansionProjectInstance(string entryPointFilePath) { var projectRootElement = ProjectRootElement.Create(); projectRootElement.FullPath = VirtualProjectBuilder.GetVirtualProjectPath(entryPointFilePath); return new ProjectInstance(projectRootElement); } private static string NormalizePath(string path) => path.Replace('\\', '/'); }