diff --git a/src/Cli/dotnet/SlnFileExtensions.cs b/src/Cli/dotnet/SlnFileExtensions.cs index 0a981b9e2429..dfad5ff9c036 100644 --- a/src/Cli/dotnet/SlnFileExtensions.cs +++ b/src/Cli/dotnet/SlnFileExtensions.cs @@ -6,536 +6,636 @@ using Microsoft.Build.Execution; using Microsoft.DotNet.Cli.Sln.Internal; using Microsoft.DotNet.Cli.Utils; -using System.Collections.Generic; -namespace Microsoft.DotNet.Tools.Common +namespace Microsoft.DotNet.Tools.Common; + +internal static class SlnFileExtensions { - internal static class SlnFileExtensions + public static void AddProject(this SlnFile slnFile, string fullProjectPath, IList solutionFolders) { - public static void AddProject(this SlnFile slnFile, string fullProjectPath, IList solutionFolders) + ArgumentException.ThrowIfNullOrEmpty(fullProjectPath); + + var relativeProjectPath = Path.GetRelativePath( + PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory), + fullProjectPath); + + if (slnFile.Projects.Any((p) => + string.Equals(p.FilePath, relativeProjectPath, StringComparison.OrdinalIgnoreCase))) + { + Reporter.Output.WriteLine(string.Format( + CommonLocalizableStrings.SolutionAlreadyContainsProject, + slnFile.FullPath, + relativeProjectPath)); + } + else { - if (string.IsNullOrEmpty(fullProjectPath)) + ProjectRootElement rootElement = null; + ProjectInstance projectInstance = null; + try + { + rootElement = ProjectRootElement.Open(fullProjectPath); + projectInstance = new ProjectInstance(rootElement); + } + catch (InvalidProjectFileException e) { - throw new ArgumentException(); + Reporter.Error.WriteLine(string.Format( + CommonLocalizableStrings.InvalidProjectWithExceptionMessage, + fullProjectPath, + e.Message)); + return; } - var relativeProjectPath = Path.GetRelativePath( - PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory), - fullProjectPath); + var slnProject = new SlnProject + { + Id = projectInstance.GetProjectId(), + TypeGuid = rootElement.GetProjectTypeGuid() ?? projectInstance.GetDefaultProjectTypeGuid(), + Name = Path.GetFileNameWithoutExtension(relativeProjectPath), + FilePath = relativeProjectPath + }; - if (slnFile.Projects.Any((p) => - string.Equals(p.FilePath, relativeProjectPath, StringComparison.OrdinalIgnoreCase))) + if (string.IsNullOrEmpty(slnProject.TypeGuid)) { - Reporter.Output.WriteLine(string.Format( - CommonLocalizableStrings.SolutionAlreadyContainsProject, - slnFile.FullPath, - relativeProjectPath)); + Reporter.Error.WriteLine( + string.Format( + CommonLocalizableStrings.UnsupportedProjectType, + projectInstance.FullPath)); + return; } - else + + // NOTE: The order you create the sections determines the order they are written to the sln + // file. In the case of an empty sln file, in order to make sure the solution configurations + // section comes first we need to add it first. This doesn't affect correctness but does + // stop VS from re-ordering things later on. Since we are keeping the SlnFile class low-level + // it shouldn't care about the VS implementation details. That's why we handle this here. + if (AreBuildConfigurationsApplicable(slnProject.TypeGuid)) { - ProjectRootElement rootElement = null; - ProjectInstance projectInstance = null; - try - { - rootElement = ProjectRootElement.Open(fullProjectPath); - projectInstance = new ProjectInstance(rootElement); - } - catch (InvalidProjectFileException e) - { - Reporter.Error.WriteLine(string.Format( - CommonLocalizableStrings.InvalidProjectWithExceptionMessage, - fullProjectPath, - e.Message)); - return; - } + slnFile.AddDefaultBuildConfigurations(); - var slnProject = new SlnProject - { - Id = projectInstance.GetProjectId(), - TypeGuid = rootElement.GetProjectTypeGuid() ?? projectInstance.GetDefaultProjectTypeGuid(), - Name = Path.GetFileNameWithoutExtension(relativeProjectPath), - FilePath = relativeProjectPath - }; + slnFile.MapSolutionConfigurationsToProject( + projectInstance, + slnFile.ProjectConfigurationsSection.GetOrCreatePropertySet(slnProject.Id)); + } - if (string.IsNullOrEmpty(slnProject.TypeGuid)) - { - Reporter.Error.WriteLine( - string.Format( - CommonLocalizableStrings.UnsupportedProjectType, - projectInstance.FullPath)); - return; - } + SetupSolutionFolders(slnFile, solutionFolders, relativeProjectPath, slnProject); - // NOTE: The order you create the sections determines the order they are written to the sln - // file. In the case of an empty sln file, in order to make sure the solution configurations - // section comes first we need to add it first. This doesn't affect correctness but does - // stop VS from re-ordering things later on. Since we are keeping the SlnFile class low-level - // it shouldn't care about the VS implementation details. That's why we handle this here. - if (AreBuildConfigurationsApplicable(slnProject.TypeGuid)) - { - slnFile.AddDefaultBuildConfigurations(); + slnFile.Projects.Add(slnProject); - slnFile.MapSolutionConfigurationsToProject( - projectInstance, - slnFile.ProjectConfigurationsSection.GetOrCreatePropertySet(slnProject.Id)); - } + Reporter.Output.WriteLine( + string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, relativeProjectPath)); + } + } - SetupSolutionFolders(slnFile, solutionFolders, relativeProjectPath, slnProject); + public static void AddSolutionFolder(this SlnFile slnFile, string fullFolderPath, IList solutionFolders) + { + ArgumentException.ThrowIfNullOrEmpty(fullFolderPath); - slnFile.Projects.Add(slnProject); + var relativeProjectPath = Path.GetRelativePath( + PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory), + fullFolderPath); - Reporter.Output.WriteLine( - string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, relativeProjectPath)); - } + if (slnFile.Projects.Any(p => string.Equals(p.FilePath, relativeProjectPath, StringComparison.OrdinalIgnoreCase))) + { + Reporter.Output.WriteLine(string.Format( + Tools.Sln.LocalizableStrings.SolutionAlreadyContainsFolder, + slnFile.FullPath, + relativeProjectPath)); } - - private static bool AreBuildConfigurationsApplicable(string projectTypeGuid) + else { - return !projectTypeGuid.Equals(ProjectTypeGuids.SharedProjectGuid, StringComparison.OrdinalIgnoreCase); + var slnProject = new SlnProject + { + Id = Guid.NewGuid().ToString("B").ToUpper(), + TypeGuid = ProjectTypeGuids.SolutionFolderGuid, + Name = Path.GetFileNameWithoutExtension(relativeProjectPath), + FilePath = relativeProjectPath, + }; + + SetupSolutionFolders(slnFile, solutionFolders, relativeProjectPath, slnProject); + + slnFile.Projects.Add(slnProject); + + Reporter.Output.WriteLine( + string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, relativeProjectPath)); } + } + + public static bool SolutionFolderContainsSolutionItem(this SlnFile slnFile, string solutionFolder, string relativeFilePath) => + slnFile.Projects + .Any(p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid + && StringComparer.OrdinalIgnoreCase.Equals(p.Name, solutionFolder) + && p.ContainsSolutionItem(Path.GetFileName(relativeFilePath))); + + private static bool AreBuildConfigurationsApplicable(string projectTypeGuid) + { + return !projectTypeGuid.Equals(ProjectTypeGuids.SharedProjectGuid, StringComparison.OrdinalIgnoreCase); + } - private static void SetupSolutionFolders(SlnFile slnFile, IList solutionFolders, string relativeProjectPath, SlnProject slnProject) + /// + /// Adds to , creating solution folders as necessary. + /// + /// When adding to the solution folder "b" inside "a" then should be ["a", "b"] + /// + public static void AddSolutionItem(this SlnFile slnFile, string relativeFilePath, IList solutionFolderParts) + { + ArgumentException.ThrowIfNullOrEmpty(relativeFilePath); + + if (solutionFolderParts == null) { - if (solutionFolders != null) - { - if (solutionFolders.Any()) - { - // Before adding a solution folder, check if the name conflicts with any existing projects in the solution - var duplicateProjects = slnFile.Projects.Where(p => solutionFolders.Contains(p.Name) - && p.TypeGuid != ProjectTypeGuids.SolutionFolderGuid).ToList(); - foreach (SlnProject duplicateProject in duplicateProjects) - { - slnFile.AddSolutionFolders(duplicateProject, new List() { Path.GetDirectoryName(duplicateProject.FilePath) }); - } - } - else - { - // If a project and solution folder have the same name, add it's own folder as a solution folder - // eg. foo\extensions.csproj and extensions\library\library.csproj would have a project and solution folder with conflicting names - var duplicateProject = slnFile.Projects.Where(p => string.Equals(p.Name, slnProject.Name, StringComparison.OrdinalIgnoreCase) - && p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid).FirstOrDefault(); - if (duplicateProject != null) - { - // Try making a new folder for the project to put it under so we can still add it despite there being one with the same name already in the parent folder - slnFile.AddSolutionFolders(slnProject, new List() { Path.GetDirectoryName(relativeProjectPath) }); - } - } - // Even if we added a solution folder above for a duplicate, we still need to add the expected folder for the current project - slnFile.AddSolutionFolders(slnProject, solutionFolders); - } + var defaultSolutionFolder = slnFile.GetOrCreateSolutionFolder(); + slnFile.AddSolutionItemToSolutionFolder(defaultSolutionFolder.Id, relativeFilePath); + return; } - private static void AddDefaultBuildConfigurations(this SlnFile slnFile) + var nestedProjectsSection = solutionFolderParts.Count > 1 + ? slnFile.Sections.GetOrCreateSection("NestedProjects", SlnSectionType.PreProcess) + : slnFile.Sections.GetSection("NestedProjects", SlnSectionType.PreProcess); + + // Get or create all solution folders, starting from the one at the root + for (int i = 0; i < solutionFolderParts.Count; i++) { - var configurationsSection = slnFile.SolutionConfigurationsSection; + string currentSolutionFolderName = solutionFolderParts[i]; + SlnProject currentSolutionFolder = slnFile.GetOrCreateSolutionFolder(currentSolutionFolderName); - if (!configurationsSection.IsEmpty) + if (i > 0 && nestedProjectsSection != null) { - return; + string previousSolutionFolderName = solutionFolderParts[i - 1]; + SlnProject previousSolutionFolder = slnFile.GetOrCreateSolutionFolder(previousSolutionFolderName); + // If a solution folder is nested inside another solution folder, + // add it to the NestedProjects section (only if it's not there yet) + nestedProjectsSection.Properties.TryAdd(currentSolutionFolder.Id, previousSolutionFolder.Id); } - var defaultConfigurations = new List() + // Once all solution folders exist, add the solution item to the innemrost solution folder + if (i == solutionFolderParts.Count - 1) { - "Debug|Any CPU", - "Debug|x64", - "Debug|x86", - "Release|Any CPU", - "Release|x64", - "Release|x86", - }; - - foreach (var config in defaultConfigurations) - { - configurationsSection[config] = config; + slnFile.AddSolutionItemToSolutionFolder(currentSolutionFolder.Id, relativeFilePath); } } + } - private static void MapSolutionConfigurationsToProject( - this SlnFile slnFile, - ProjectInstance projectInstance, - SlnPropertySet solutionProjectConfigs) + private static void AddSolutionItemToSolutionFolder(this SlnFile slnFile, string solutionFolderId, string relativeFilePath) + { + var solutionFolder = slnFile.Projects.GetProject(solutionFolderId); + var solutionItemsSection = solutionFolder.Sections.GetOrCreateSection("SolutionItems", SlnSectionType.PreProcess); + var solutionItems = new Dictionary(solutionItemsSection.GetContent(), StringComparer.OrdinalIgnoreCase); + + if (solutionItems.TryAdd(relativeFilePath, relativeFilePath)) + { + solutionItemsSection.SetContent(solutionItems); + Reporter.Output.WriteLine( + string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, relativeFilePath, solutionFolder.Name)); + } + else { - var (projectConfigurations, defaultProjectConfiguration) = GetKeysDictionary(projectInstance.GetConfigurations()); - var (projectPlatforms, defaultProjectPlatform) = GetKeysDictionary(projectInstance.GetPlatforms()); + throw new GracefulException(string.Format( + Tools.Sln.LocalizableStrings.SolutionAlreadyContainsFile, + slnFile.FullPath, + relativeFilePath)); + } + } - foreach (var solutionConfigKey in slnFile.SolutionConfigurationsSection.Keys) - { - var projectConfigKey = MapSolutionConfigKeyToProjectConfigKey( - solutionConfigKey, - projectConfigurations, - defaultProjectConfiguration, - projectPlatforms, - defaultProjectPlatform); - if (projectConfigKey == null) - { - continue; - } + private static SlnProject GetOrCreateSolutionFolder(this SlnFile slnFile, string solutionFolderName = "Solution Items") + { + var existingSolutionFolder = slnFile.Projects.SingleOrDefault( + p => StringComparer.OrdinalIgnoreCase.Equals(p.Name, solutionFolderName)); + if (existingSolutionFolder != null) { return existingSolutionFolder; } - var activeConfigKey = $"{solutionConfigKey}.ActiveCfg"; - if (!solutionProjectConfigs.ContainsKey(activeConfigKey)) - { - solutionProjectConfigs[activeConfigKey] = projectConfigKey; - } + var solutionFolder = new SlnProject + { + Id = Guid.NewGuid().ToString("B").ToUpper(), + TypeGuid = ProjectTypeGuids.SolutionFolderGuid, + Name = solutionFolderName, + FilePath = solutionFolderName, + }; + slnFile.Projects.Add(solutionFolder); + return solutionFolder; + } - var buildKey = $"{solutionConfigKey}.Build.0"; - if (!solutionProjectConfigs.ContainsKey(buildKey)) - { - solutionProjectConfigs[buildKey] = projectConfigKey; - } - } + private static void SetupSolutionFolders(SlnFile slnFile, IList solutionFolders, string relativeProjectPath, SlnProject slnProject) + { + if (solutionFolders == null) + { + return; } - private static (Dictionary Keys, string DefaultKey) GetKeysDictionary(IEnumerable keys) + if (solutionFolders.Any()) { - // A dictionary mapping key -> key is used instead of a HashSet so the original case of the key can be retrieved from the set - var dictionary = new Dictionary(StringComparer.CurrentCultureIgnoreCase); - - foreach (var key in keys) + // Before adding a solution folder, check if the name conflicts with any existing projects in the solution + var duplicateProjects = slnFile.Projects.Where(p => solutionFolders.Contains(p.Name) + && p.TypeGuid != ProjectTypeGuids.SolutionFolderGuid).ToList(); + foreach (SlnProject duplicateProject in duplicateProjects) { - dictionary[key] = key; + slnFile.AddSolutionFolders(duplicateProject, [Path.GetDirectoryName(duplicateProject.FilePath)]); } - - return (dictionary, keys.FirstOrDefault()); } - - private static string GetMatchingProjectKey(IDictionary projectKeys, string solutionKey) + else { - string projectKey; - if (projectKeys.TryGetValue(solutionKey, out projectKey)) + // If a project and solution folder have the same name, add its own folder as a solution folder + // eg. foo\extensions.csproj and extensions\library\library.csproj would have a project and solution folder with conflicting names + var duplicateProject = slnFile.Projects.Where(p => string.Equals(p.Name, slnProject.Name, StringComparison.OrdinalIgnoreCase) + && p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid).FirstOrDefault(); + if (duplicateProject != null) { - return projectKey; + // Try making a new folder for the project to put it under so we can still add it despite there being one with the same name already in the parent folder + slnFile.AddSolutionFolders(slnProject, [Path.GetDirectoryName(relativeProjectPath)]); } + } + // Even if we added a solution folder above for a duplicate, we still need to add the expected folder for the current project + slnFile.AddSolutionFolders(slnProject, solutionFolders); + } - var keyWithoutWhitespace = string.Concat(solutionKey.Where(c => !char.IsWhiteSpace(c))); - if (projectKeys.TryGetValue(keyWithoutWhitespace, out projectKey)) - { - return projectKey; - } + private static void AddDefaultBuildConfigurations(this SlnFile slnFile) + { + var configurationsSection = slnFile.SolutionConfigurationsSection; - return null; + if (!configurationsSection.IsEmpty) + { + return; } - private static string MapSolutionConfigKeyToProjectConfigKey( - string solutionConfigKey, - Dictionary projectConfigurations, - string defaultProjectConfiguration, - Dictionary projectPlatforms, - string defaultProjectPlatform) + var defaultConfigurations = new List() { - var pair = solutionConfigKey.Split(new char[] { '|' }, 2); - if (pair.Length != 2) + "Debug|Any CPU", + "Debug|x64", + "Debug|x86", + "Release|Any CPU", + "Release|x64", + "Release|x86", + }; + + foreach (var config in defaultConfigurations) + { + configurationsSection[config] = config; + } + } + + private static void MapSolutionConfigurationsToProject( + this SlnFile slnFile, + ProjectInstance projectInstance, + SlnPropertySet solutionProjectConfigs) + { + var (projectConfigurations, defaultProjectConfiguration) = GetKeysDictionary(projectInstance.GetConfigurations()); + var (projectPlatforms, defaultProjectPlatform) = GetKeysDictionary(projectInstance.GetPlatforms()); + + foreach (var solutionConfigKey in slnFile.SolutionConfigurationsSection.Keys) + { + var projectConfigKey = MapSolutionConfigKeyToProjectConfigKey( + solutionConfigKey, + projectConfigurations, + defaultProjectConfiguration, + projectPlatforms, + defaultProjectPlatform); + if (projectConfigKey == null) { - return null; + continue; } - var projectConfiguration = GetMatchingProjectKey(projectConfigurations, pair[0]) ?? defaultProjectConfiguration; - if (projectConfiguration == null) + var activeConfigKey = $"{solutionConfigKey}.ActiveCfg"; + if (!solutionProjectConfigs.ContainsKey(activeConfigKey)) { - return null; + solutionProjectConfigs[activeConfigKey] = projectConfigKey; } - var projectPlatform = GetMatchingProjectKey(projectPlatforms, pair[1]) ?? defaultProjectPlatform; - if (projectPlatform == null) + var buildKey = $"{solutionConfigKey}.Build.0"; + if (!solutionProjectConfigs.ContainsKey(buildKey)) { - return null; + solutionProjectConfigs[buildKey] = projectConfigKey; } - - // VS stores "Any CPU" platform in the solution regardless of how it is named at the project level - return $"{projectConfiguration}|{(projectPlatform == "AnyCPU" ? "Any CPU" : projectPlatform)}"; } + } + + private static (Dictionary Keys, string DefaultKey) GetKeysDictionary(IEnumerable keys) + { + // A dictionary mapping key -> key is used instead of a HashSet so the original case of the key can be retrieved from the set + var dictionary = new Dictionary(StringComparer.CurrentCultureIgnoreCase); - private static void AddSolutionFolders(this SlnFile slnFile, SlnProject slnProject, IList solutionFolders) + foreach (var key in keys) { - if (solutionFolders.Any()) - { - var nestedProjectsSection = slnFile.Sections.GetOrCreateSection( - "NestedProjects", - SlnSectionType.PreProcess); + dictionary[key] = key; + } - var pathToGuidMap = slnFile.GetSolutionFolderPaths(nestedProjectsSection.Properties); + return (dictionary, keys.FirstOrDefault()); + } - if (slnFile.HasSolutionFolder(nestedProjectsSection.Properties, slnProject)) - { - return; - } + private static string GetMatchingProjectKey(Dictionary projectKeys, string solutionKey) + { + if (projectKeys.TryGetValue(solutionKey, out string projectKey)) + { + return projectKey; + } - string parentDirGuid = null; - var solutionFolderHierarchy = string.Empty; - foreach (var dir in solutionFolders) - { - solutionFolderHierarchy = Path.Combine(solutionFolderHierarchy, dir); - if (pathToGuidMap.ContainsKey(solutionFolderHierarchy)) - { - parentDirGuid = pathToGuidMap[solutionFolderHierarchy]; - } - else - { + var keyWithoutWhitespace = string.Concat(solutionKey.Where(c => !char.IsWhiteSpace(c))); + if (projectKeys.TryGetValue(keyWithoutWhitespace, out projectKey)) + { + return projectKey; + } - if(HasDuplicateNameForSameValueOfNestedProjects(nestedProjectsSection, dir, parentDirGuid, slnFile.Projects)) - { - throw new GracefulException(CommonLocalizableStrings.SolutionFolderAlreadyContainsProject, slnFile.FullPath, slnProject.Name, slnFile.Projects.FirstOrDefault(p => p.Id == parentDirGuid).Name); - } + return null; + } - var solutionFolder = new SlnProject - { - Id = Guid.NewGuid().ToString("B").ToUpper(), - TypeGuid = ProjectTypeGuids.SolutionFolderGuid, - Name = dir, - FilePath = dir - }; + private static string MapSolutionConfigKeyToProjectConfigKey( + string solutionConfigKey, + Dictionary projectConfigurations, + string defaultProjectConfiguration, + Dictionary projectPlatforms, + string defaultProjectPlatform) + { + var pair = solutionConfigKey.Split(['|'], 2); + if (pair.Length != 2) + { + return null; + } - slnFile.Projects.Add(solutionFolder); + var projectConfiguration = GetMatchingProjectKey(projectConfigurations, pair[0]) ?? defaultProjectConfiguration; + if (projectConfiguration == null) + { + return null; + } - if (parentDirGuid != null) - { - nestedProjectsSection.Properties[solutionFolder.Id] = parentDirGuid; - } - parentDirGuid = solutionFolder.Id; - } - } - if (HasDuplicateNameForSameValueOfNestedProjects(nestedProjectsSection, slnProject.Name, parentDirGuid, slnFile.Projects)) - { - throw new GracefulException(CommonLocalizableStrings.SolutionFolderAlreadyContainsProject, slnFile.FullPath, slnProject.Name, slnFile.Projects.FirstOrDefault(p => p.Id == parentDirGuid).Name); - } - nestedProjectsSection.Properties[slnProject.Id] = parentDirGuid; - } + var projectPlatform = GetMatchingProjectKey(projectPlatforms, pair[1]) ?? defaultProjectPlatform; + if (projectPlatform == null) + { + return null; } - private static bool HasDuplicateNameForSameValueOfNestedProjects(SlnSection nestedProjectsSection, string name, string value, IList projects) + // VS stores "Any CPU" platform in the solution regardless of how it is named at the project level + return $"{projectConfiguration}|{(projectPlatform == "AnyCPU" ? "Any CPU" : projectPlatform)}"; + } + + private static void AddSolutionFolders(this SlnFile slnFile, SlnProject slnProject, IList solutionFolders) + { + if (solutionFolders.Any()) { - foreach (var property in nestedProjectsSection.Properties) + var nestedProjectsSection = slnFile.Sections.GetOrCreateSection( + "NestedProjects", + SlnSectionType.PreProcess); + + var pathToGuidMap = slnFile.GetSolutionFolderPaths(nestedProjectsSection.Properties); + + if (nestedProjectsSection.Properties.ContainsKey(slnProject.Id)) + { + return; + } + + string parentDirGuid = null; + var solutionFolderHierarchy = string.Empty; + foreach (var dir in solutionFolders) { - if (property.Value == value) + solutionFolderHierarchy = Path.Combine(solutionFolderHierarchy, dir); + if (pathToGuidMap.TryGetValue(solutionFolderHierarchy, out string? guid)) + { + parentDirGuid = guid; + } + else { - var existingProject = projects.FirstOrDefault(p => p.Id == property.Key); - if (existingProject != null && existingProject.Name == name) + if(HasDuplicateNameForSameValueOfNestedProjects(nestedProjectsSection, dir, parentDirGuid, slnFile.Projects)) + { + throw new GracefulException(CommonLocalizableStrings.SolutionFolderAlreadyContainsProject, slnFile.FullPath, slnProject.Name, slnFile.Projects.FirstOrDefault(p => p.Id == parentDirGuid).Name); + } + + var solutionFolder = new SlnProject + { + Id = Guid.NewGuid().ToString("B").ToUpper(), + TypeGuid = ProjectTypeGuids.SolutionFolderGuid, + Name = dir, + FilePath = dir + }; + + slnFile.Projects.Add(solutionFolder); + + if (parentDirGuid != null) { - return true; + nestedProjectsSection.Properties[solutionFolder.Id] = parentDirGuid; } + parentDirGuid = solutionFolder.Id; } } - return false; + if (HasDuplicateNameForSameValueOfNestedProjects(nestedProjectsSection, slnProject.Name, parentDirGuid, slnFile.Projects)) + { + throw new GracefulException(CommonLocalizableStrings.SolutionFolderAlreadyContainsProject, slnFile.FullPath, slnProject.Name, slnFile.Projects.FirstOrDefault(p => p.Id == parentDirGuid).Name); + } + nestedProjectsSection.Properties[slnProject.Id] = parentDirGuid; } + } - private static IDictionary GetSolutionFolderPaths( - this SlnFile slnFile, - SlnPropertySet nestedProjects) + private static bool HasDuplicateNameForSameValueOfNestedProjects(SlnSection nestedProjectsSection, string name, string value, IList projects) + { + foreach (var property in nestedProjectsSection.Properties) { - var solutionFolderPaths = new Dictionary(StringComparer.OrdinalIgnoreCase); - - var solutionFolderProjects = slnFile.Projects.GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid); - foreach (var slnProject in solutionFolderProjects) + if (property.Value == value) { - var path = slnProject.FilePath; - var id = slnProject.Id; - while (nestedProjects.ContainsKey(id)) + var existingProject = projects.FirstOrDefault(p => p.Id == property.Key); + + if (existingProject != null && existingProject.Name == name) { - id = nestedProjects[id]; - var parentSlnProject = solutionFolderProjects.Where(p => p.Id == id).SingleOrDefault(); - if (parentSlnProject == null) // see: https://github.com/dotnet/sdk/pull/28811 - throw new GracefulException(CommonLocalizableStrings.CorruptSolutionProjectFolderStructure, slnFile.FullPath, id); - path = Path.Combine(parentSlnProject.FilePath, path); + return true; } - - solutionFolderPaths[path] = slnProject.Id; } - - return solutionFolderPaths; } + return false; + } - private static bool HasSolutionFolder( - this SlnFile slnFile, - SlnPropertySet properties, - SlnProject slnProject) - { - return properties.ContainsKey(slnProject.Id); - } + private static Dictionary GetSolutionFolderPaths( + this SlnFile slnFile, + SlnPropertySet nestedProjects) + { + var solutionFolderPaths = new Dictionary(StringComparer.OrdinalIgnoreCase); - public static bool RemoveProject(this SlnFile slnFile, string projectPath) + var solutionFolderProjects = slnFile.Projects.GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid); + foreach (var slnProject in solutionFolderProjects) { - if (string.IsNullOrEmpty(projectPath)) + var path = slnProject.FilePath; + var id = slnProject.Id; + while (nestedProjects.ContainsKey(id)) { - throw new ArgumentException(); + id = nestedProjects[id]; + var parentSlnProject = solutionFolderProjects.SingleOrDefault(p => p.Id == id) + // see: https://github.com/dotnet/sdk/pull/28811 + ?? throw new GracefulException(CommonLocalizableStrings.CorruptSolutionProjectFolderStructure, slnFile.FullPath, id); + path = Path.Combine(parentSlnProject.FilePath, path); } - var projectPathNormalized = PathUtility.GetPathWithDirectorySeparator(projectPath); + solutionFolderPaths[path] = slnProject.Id; + } - var projectsToRemove = slnFile.Projects.Where((p) => - string.Equals(p.FilePath, projectPathNormalized, StringComparison.OrdinalIgnoreCase)).ToList(); + return solutionFolderPaths; + } - bool projectRemoved = false; - if (projectsToRemove.Count == 0) - { - Reporter.Output.WriteLine(string.Format( - CommonLocalizableStrings.ProjectNotFoundInTheSolution, - projectPath)); - } - else - { - foreach (var slnProject in projectsToRemove) - { - var buildConfigsToRemove = slnFile.ProjectConfigurationsSection.GetPropertySet(slnProject.Id); - if (buildConfigsToRemove != null) - { - slnFile.ProjectConfigurationsSection.Remove(buildConfigsToRemove); - } + public static bool RemoveProject(this SlnFile slnFile, string projectPath) + { + ArgumentException.ThrowIfNullOrEmpty(projectPath); - var nestedProjectsSection = slnFile.Sections.GetSection( - "NestedProjects", - SlnSectionType.PreProcess); - if (nestedProjectsSection != null && nestedProjectsSection.Properties.ContainsKey(slnProject.Id)) - { - nestedProjectsSection.Properties.Remove(slnProject.Id); - } + var projectPathNormalized = PathUtility.GetPathWithDirectorySeparator(projectPath); - slnFile.Projects.Remove(slnProject); - Reporter.Output.WriteLine( - string.Format(CommonLocalizableStrings.ProjectRemovedFromTheSolution, slnProject.FilePath)); - } + var projectsToRemove = slnFile.Projects.Where((p) => + string.Equals(p.FilePath, projectPathNormalized, StringComparison.OrdinalIgnoreCase)).ToList(); - foreach (var project in slnFile.Projects) + bool projectRemoved = false; + if (projectsToRemove.Count == 0) + { + Reporter.Output.WriteLine(string.Format( + CommonLocalizableStrings.ProjectNotFoundInTheSolution, + projectPath)); + } + else + { + foreach (var slnProject in projectsToRemove) + { + var buildConfigsToRemove = slnFile.ProjectConfigurationsSection.GetPropertySet(slnProject.Id); + if (buildConfigsToRemove != null) { - var dependencies = project.Dependencies; - if (dependencies == null) - { - continue; - } - - dependencies.SkipIfEmpty = true; + slnFile.ProjectConfigurationsSection.Remove(buildConfigsToRemove); + } - foreach (var removed in projectsToRemove) - { - dependencies.Properties.Remove(removed.Id); - } + var nestedProjectsSection = slnFile.Sections.GetSection( + "NestedProjects", + SlnSectionType.PreProcess); + if (nestedProjectsSection != null && nestedProjectsSection.Properties.ContainsKey(slnProject.Id)) + { + nestedProjectsSection.Properties.Remove(slnProject.Id); } - projectRemoved = true; + slnFile.Projects.Remove(slnProject); + Reporter.Output.WriteLine( + string.Format(CommonLocalizableStrings.ProjectRemovedFromTheSolution, slnProject.FilePath)); } - return projectRemoved; - } - - public static void RemoveEmptyConfigurationSections(this SlnFile slnFile) - { - if (slnFile.Projects.Count == 0) + foreach (var project in slnFile.Projects) { - var solutionConfigs = slnFile.Sections.GetSection("SolutionConfigurationPlatforms"); - if (solutionConfigs != null) + var dependencies = project.Dependencies; + if (dependencies == null) { - slnFile.Sections.Remove(solutionConfigs); + continue; } - var projectConfigs = slnFile.Sections.GetSection("ProjectConfigurationPlatforms"); - if (projectConfigs != null) + dependencies.SkipIfEmpty = true; + + foreach (var removed in projectsToRemove) { - slnFile.Sections.Remove(projectConfigs); + dependencies.Properties.Remove(removed.Id); } } + + projectRemoved = true; } - public static void RemoveEmptySolutionFolders(this SlnFile slnFile) + return projectRemoved; + } + + public static void RemoveEmptyConfigurationSections(this SlnFile slnFile) + { + if (slnFile.Projects.Count == 0) { - var solutionFolderProjects = slnFile.Projects - .GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid) - .ToList(); + var solutionConfigs = slnFile.Sections.GetSection("SolutionConfigurationPlatforms"); + if (solutionConfigs != null) + { + slnFile.Sections.Remove(solutionConfigs); + } - if (solutionFolderProjects.Any()) + var projectConfigs = slnFile.Sections.GetSection("ProjectConfigurationPlatforms"); + if (projectConfigs != null) { - var nestedProjectsSection = slnFile.Sections.GetSection( - "NestedProjects", - SlnSectionType.PreProcess); + slnFile.Sections.Remove(projectConfigs); + } + } + } + + public static void RemoveEmptySolutionFolders(this SlnFile slnFile) + { + var solutionFolderProjects = slnFile.Projects + .GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid) + .ToList(); + + if (solutionFolderProjects.Count != 0) + { + var nestedProjectsSection = slnFile.Sections.GetSection( + "NestedProjects", + SlnSectionType.PreProcess); - if (nestedProjectsSection == null) + if (nestedProjectsSection == null) + { + foreach (var solutionFolderProject in solutionFolderProjects) { - foreach (var solutionFolderProject in solutionFolderProjects) + if (solutionFolderProject.Sections.Count == 0) { - if (solutionFolderProject.Sections.Count() == 0) - { - slnFile.Projects.Remove(solutionFolderProject); - } + slnFile.Projects.Remove(solutionFolderProject); } } - else - { - var solutionFoldersInUse = slnFile.GetSolutionFoldersThatContainProjectsInItsHierarchy( - nestedProjectsSection.Properties); + } + else + { + var solutionFoldersInUse = slnFile.GetSolutionFoldersThatContainProjectsInItsHierarchy( + nestedProjectsSection.Properties); - solutionFoldersInUse.UnionWith(slnFile.GetSolutionFoldersThatContainSolutionItemsInItsHierarchy( - nestedProjectsSection.Properties)); + solutionFoldersInUse.UnionWith(slnFile.GetSolutionFoldersThatContainSolutionItemsInItsHierarchy( + nestedProjectsSection.Properties)); - foreach (var solutionFolderProject in solutionFolderProjects) + foreach (var solutionFolderProject in solutionFolderProjects) + { + if (!solutionFoldersInUse.Contains(solutionFolderProject.Id)) { - if (!solutionFoldersInUse.Contains(solutionFolderProject.Id)) + nestedProjectsSection.Properties.Remove(solutionFolderProject.Id); + if (solutionFolderProject.Sections.Count == 0) { - nestedProjectsSection.Properties.Remove(solutionFolderProject.Id); - if (solutionFolderProject.Sections.Count() == 0) - { - slnFile.Projects.Remove(solutionFolderProject); - } + slnFile.Projects.Remove(solutionFolderProject); } } + } - if (nestedProjectsSection.IsEmpty) - { - slnFile.Sections.Remove(nestedProjectsSection); - } + if (nestedProjectsSection.IsEmpty) + { + slnFile.Sections.Remove(nestedProjectsSection); } } } + } - private static HashSet GetSolutionFoldersThatContainProjectsInItsHierarchy( - this SlnFile slnFile, - SlnPropertySet nestedProjects) - { - var solutionFoldersInUse = new HashSet(); + private static HashSet GetSolutionFoldersThatContainProjectsInItsHierarchy( + this SlnFile slnFile, + SlnPropertySet nestedProjects) + { + var solutionFoldersInUse = new HashSet(); - IEnumerable nonSolutionFolderProjects; - nonSolutionFolderProjects = slnFile.Projects.GetProjectsNotOfType( - ProjectTypeGuids.SolutionFolderGuid); + IEnumerable nonSolutionFolderProjects; + nonSolutionFolderProjects = slnFile.Projects.GetProjectsNotOfType( + ProjectTypeGuids.SolutionFolderGuid); - foreach (var nonSolutionFolderProject in nonSolutionFolderProjects) + foreach (var nonSolutionFolderProject in nonSolutionFolderProjects) + { + var id = nonSolutionFolderProject.Id; + while (nestedProjects.ContainsKey(id)) { - var id = nonSolutionFolderProject.Id; - while (nestedProjects.ContainsKey(id)) - { - id = nestedProjects[id]; - solutionFoldersInUse.Add(id); - } + id = nestedProjects[id]; + solutionFoldersInUse.Add(id); } - - return solutionFoldersInUse; } - private static HashSet GetSolutionFoldersThatContainSolutionItemsInItsHierarchy( - this SlnFile slnFile, - SlnPropertySet nestedProjects) - { - var solutionFoldersInUse = new HashSet(); + return solutionFoldersInUse; + } - var solutionItemsFolderProjects = slnFile.Projects - .GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid) - .Where(ContainsSolutionItems); + private static HashSet GetSolutionFoldersThatContainSolutionItemsInItsHierarchy( + this SlnFile slnFile, + SlnPropertySet nestedProjects) + { + var solutionFoldersInUse = new HashSet(); + + var solutionItemsFolderProjects = slnFile.Projects + .GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid) + .Where(p => p.GetSolutionItemsSectionOrDefault() != null); - foreach (var solutionItemsFolderProject in solutionItemsFolderProjects) + foreach (var solutionItemsFolderProject in solutionItemsFolderProjects) + { + var id = solutionItemsFolderProject.Id; + solutionFoldersInUse.Add(id); + + while (nestedProjects.ContainsKey(id)) { - var id = solutionItemsFolderProject.Id; + id = nestedProjects[id]; solutionFoldersInUse.Add(id); - - while (nestedProjects.ContainsKey(id)) - { - id = nestedProjects[id]; - solutionFoldersInUse.Add(id); - } } - - return solutionFoldersInUse; } - private static bool ContainsSolutionItems(SlnProject project) - { - return project.Sections - .GetSection("SolutionItems", SlnSectionType.PreProcess) != null; - } + return solutionFoldersInUse; } } diff --git a/src/Cli/dotnet/SlnProjectExtensions.cs b/src/Cli/dotnet/SlnProjectExtensions.cs index 405c3b43cb88..491e067ddf56 100644 --- a/src/Cli/dotnet/SlnProjectExtensions.cs +++ b/src/Cli/dotnet/SlnProjectExtensions.cs @@ -32,5 +32,18 @@ public static string GetFullSolutionFolderPath(this SlnProject slnProject) return path; } + + public static SlnSection GetSolutionItemsSectionOrDefault(this SlnProject project) => + project.Sections.GetSection("SolutionItems", SlnSectionType.PreProcess); + + public static bool ContainsSolutionItem(this SlnProject project, string solutionItemName) + { + var section = project.GetSolutionItemsSectionOrDefault(); + if (section == null) { return false; } + + // solution item names are case-insensitive + return new Dictionary(section.GetContent(), StringComparer.OrdinalIgnoreCase) + .ContainsKey(solutionItemName); + } } } diff --git a/src/Cli/dotnet/commands/dotnet-sln/LocalizableStrings.resx b/src/Cli/dotnet/commands/dotnet-sln/LocalizableStrings.resx index 4748d3d4504b..737323b46ee7 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/LocalizableStrings.resx +++ b/src/Cli/dotnet/commands/dotnet-sln/LocalizableStrings.resx @@ -132,15 +132,42 @@ Projects to add or to remove from the solution. + + Could not find file `{0}`. + Add one or more projects to a solution file. + + Add one or more solution items to a solution file. + + + Add one or more solution folders to a solution file. + PROJECT_PATH + + PROJECT_PATH should not be provided for `dotnet sln add file` + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + The paths to the projects to add to the solution. + + FILE_PATH + + + The paths to the solution items to add to the solution. + + + FOLDER_PATH + + + The paths to the solution folders to add to the solution. + PROJECT_PATH @@ -150,6 +177,32 @@ Remove one or more projects from a solution file. + + You must specify at least one file to add. + + + You must specify at least one folder to add. + + + The solution {0} already contains the solution item `{1}` + + + The solution {0} already contains the solution folder `{1}` + + + The solution item `{0}` was added to the solution folder `{1}` + + + The solution folder `{0}` was added to the solution + + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Remove the specified project(s) from the solution. The project is not impacted. @@ -162,15 +215,39 @@ Project(s) - + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + Place project in root of the solution, rather than creating a solution folder. + + Place file in root of the solution, rather than creating a solution folder. + + + Place folder in root of the solution, rather than creating a solution folder. + The destination solution folder path to add the projects to. + + The destination solution folder path to add the files to. + + + The destination solution folder path to add the folders to. + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + The --solution-folder and --in-root options cannot be used together; use only one of the options. + + Cannot add the same solution to itself. + + + An existing project cannot be added as a solution item. + Display solution folder paths. diff --git a/src/Cli/dotnet/commands/dotnet-sln/SlnArgumentValidator.cs b/src/Cli/dotnet/commands/dotnet-sln/SlnArgumentValidator.cs index 5ebab9458a60..d9c80a2082e3 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/SlnArgumentValidator.cs +++ b/src/Cli/dotnet/commands/dotnet-sln/SlnArgumentValidator.cs @@ -1,60 +1,99 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.Utils; -namespace Microsoft.DotNet.Tools.Sln +namespace Microsoft.DotNet.Tools.Sln; + +internal static class SlnArgumentValidator { - internal static class SlnArgumentValidator + private static readonly SearchValues s_invalidCharactersInSolutionFolderName = SearchValues.Create("/:?\\*\"<>|"); + private static readonly string[] s_invalidSolutionFolderNames = + [ + // system reserved names per https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions + "CON", "PRN", "AUX", "NUL", + "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", + // relative path components + ".", "..", + ]; + + public enum CommandType + { + Add, + Remove + } + public static void ParseAndValidateArguments(IReadOnlyList _arguments, CommandType commandType, bool _inRoot = false, string relativeRoot = null, string subcommand = null) { - public enum CommandType + if (_arguments.Count == 0) { - Add, - Remove + string message = commandType == CommandType.Add + ? CommonLocalizableStrings.SpecifyAtLeastOneProjectToAdd + : CommonLocalizableStrings.SpecifyAtLeastOneProjectToRemove; + throw new GracefulException(message); } - public static void ParseAndValidateArguments(string _fileOrDirectory, IReadOnlyCollection _arguments, CommandType commandType, bool _inRoot = false, string relativeRoot = null) + + bool hasRelativeRoot = !string.IsNullOrEmpty(relativeRoot); + + if (_inRoot && hasRelativeRoot) { - if (_arguments.Count == 0) - { - string message = commandType == CommandType.Add ? CommonLocalizableStrings.SpecifyAtLeastOneProjectToAdd : CommonLocalizableStrings.SpecifyAtLeastOneProjectToRemove; - throw new GracefulException(message); - } - - bool hasRelativeRoot = !string.IsNullOrEmpty(relativeRoot); - - if (_inRoot && hasRelativeRoot) - { - // These two options are mutually exclusive - throw new GracefulException(LocalizableStrings.SolutionFolderAndInRootMutuallyExclusive); - } - - var slnFile = _arguments.FirstOrDefault(path => path.EndsWith(".sln")); - if (slnFile != null) - { - string args; - if (_inRoot) - { - args = $"--{SlnAddParser.InRootOption.Name} "; - } - else if (hasRelativeRoot) - { - args = $"--{SlnAddParser.SolutionFolderOption.Name} {string.Join(" ", relativeRoot)} "; - } - else - { - args = ""; - } - - var projectArgs = string.Join(" ", _arguments.Where(path => !path.EndsWith(".sln"))); - string command = commandType == CommandType.Add ? "add" : "remove"; - throw new GracefulException(new string[] - { - string.Format(CommonLocalizableStrings.SolutionArgumentMisplaced, slnFile), - CommonLocalizableStrings.DidYouMean, - $" dotnet solution {slnFile} {command} {args}{projectArgs}" - }); - } + // These two options are mutually exclusive + throw new GracefulException(LocalizableStrings.SolutionFolderAndInRootMutuallyExclusive); } + + // Something is wrong if there is a .sln file as an argument, so suggest that the arguments may have been misplaced. + // However, it is possible to add .sln file as a solution item, so don't suggest in the case of dotnet sln add file. + var slnFile = _arguments.FirstOrDefault(path => path.EndsWith(".sln", StringComparison.OrdinalIgnoreCase)); + if (slnFile == null || subcommand == "file") + { + return; + } + + string options = _inRoot + ? $"{SlnAddParser.InRootOption.Name} " + : hasRelativeRoot + ? $"{SlnAddParser.SolutionFolderOption.Name} {string.Join(" ", relativeRoot)} " + : ""; + + var nonSolutionArguments = string.Join( + " ", + _arguments.Where(a => !a.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))); + + string command = commandType switch + { + CommandType.Add => "add", + CommandType.Remove => "remove", + _ => throw new InvalidOperationException($"Unable to handle command type {commandType}"), + }; + throw new GracefulException( + [ + string.Format(CommonLocalizableStrings.SolutionArgumentMisplaced, slnFile), + CommonLocalizableStrings.DidYouMean, + subcommand == null + ? $" dotnet solution {slnFile} {command} {options}{nonSolutionArguments}" + : $" dotnet solution {slnFile} {command} {subcommand} {options}{nonSolutionArguments}" + ]); + } + + public static bool IsValidSolutionFolderName(string folderName) + { + if (string.IsNullOrWhiteSpace(folderName)) + return false; + + if (folderName.AsSpan().IndexOfAny(s_invalidCharactersInSolutionFolderName) >= 0) + return false; + + if (folderName.Any(char.IsControl)) + return false; + + if (folderName.Any(char.IsSurrogate)) + return false; + + if (s_invalidSolutionFolderNames.Contains(folderName, StringComparer.OrdinalIgnoreCase)) + return false; + + return true; } } diff --git a/src/Cli/dotnet/commands/dotnet-sln/add/Program.cs b/src/Cli/dotnet/commands/dotnet-sln/add/Program.cs index ded4c5fc3968..0470b89b8fe3 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/add/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-sln/add/Program.cs @@ -7,138 +7,127 @@ using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Tools.Common; -namespace Microsoft.DotNet.Tools.Sln.Add +namespace Microsoft.DotNet.Tools.Sln.Add; + +internal class AddProjectToSolutionCommand : CommandBase { - internal class AddProjectToSolutionCommand : CommandBase - { - private readonly string _fileOrDirectory; - private readonly bool _inRoot; - private readonly IList _relativeRootSolutionFolders; - private readonly IReadOnlyCollection _arguments; + private readonly string _fileOrDirectory; + private readonly bool _inRoot; + private readonly IList _relativeRootSolutionFolders; + private readonly IReadOnlyList _arguments; - public AddProjectToSolutionCommand(ParseResult parseResult) : base(parseResult) - { - _fileOrDirectory = parseResult.GetValue(SlnCommandParser.SlnArgument); + public AddProjectToSolutionCommand(ParseResult parseResult) : base(parseResult) + { + _fileOrDirectory = parseResult.GetValue(SlnCommandParser.SlnArgument); - _arguments = parseResult.GetValue(SlnAddParser.ProjectPathArgument)?.ToArray() ?? (IReadOnlyCollection)Array.Empty(); + _arguments = parseResult.GetValue(SlnAddParser.ProjectPathArgument)?.ToArray() ?? []; - _inRoot = parseResult.GetValue(SlnAddParser.InRootOption); - string relativeRoot = parseResult.GetValue(SlnAddParser.SolutionFolderOption); + _inRoot = parseResult.GetValue(SlnAddParser.InRootOption); + string relativeRoot = parseResult.GetValue(SlnAddParser.SolutionFolderOption); - SlnArgumentValidator.ParseAndValidateArguments(_fileOrDirectory, _arguments, SlnArgumentValidator.CommandType.Add, _inRoot, relativeRoot); + SlnArgumentValidator.ParseAndValidateArguments(_arguments, SlnArgumentValidator.CommandType.Add, _inRoot, relativeRoot); - bool hasRelativeRoot = !string.IsNullOrEmpty(relativeRoot); + bool hasRelativeRoot = !string.IsNullOrEmpty(relativeRoot); - if (hasRelativeRoot) - { - relativeRoot = PathUtility.GetPathWithDirectorySeparator(relativeRoot); - _relativeRootSolutionFolders = relativeRoot.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); - } - else - { - _relativeRootSolutionFolders = null; - } + if (hasRelativeRoot) + { + relativeRoot = PathUtility.GetPathWithDirectorySeparator(relativeRoot); + _relativeRootSolutionFolders = relativeRoot.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); } - - public override int Execute() + else { - SlnFile slnFile = SlnFileFactory.CreateFromFileOrDirectory(_fileOrDirectory); - - var arguments = (_parseResult.GetValue>(SlnAddParser.ProjectPathArgument) ?? Array.Empty()).ToList().AsReadOnly(); - if (arguments.Count == 0) - { - throw new GracefulException(CommonLocalizableStrings.SpecifyAtLeastOneProjectToAdd); - } + _relativeRootSolutionFolders = null; + } + } - PathUtility.EnsureAllPathsExist(arguments, CommonLocalizableStrings.CouldNotFindProjectOrDirectory, true); + public override int Execute() + { + SlnFile slnFile = SlnFileFactory.CreateFromFileOrDirectory(_fileOrDirectory); - var fullProjectPaths = _arguments.Select(p => - { - var fullPath = Path.GetFullPath(p); - return Directory.Exists(fullPath) ? - MsbuildProject.GetProjectFileFromDirectory(fullPath).FullName : - fullPath; - }).ToList(); + var arguments = (_parseResult.GetValue(SlnAddParser.ProjectPathArgument) ?? []).ToList().AsReadOnly(); + if (arguments.Count == 0) + { + throw new GracefulException(CommonLocalizableStrings.SpecifyAtLeastOneProjectToAdd); + } - var preAddProjectCount = slnFile.Projects.Count; + PathUtility.EnsureAllPathsExist(arguments, CommonLocalizableStrings.CouldNotFindProjectOrDirectory, true); - foreach (var fullProjectPath in fullProjectPaths) - { - // Identify the intended solution folders - var solutionFolders = DetermineSolutionFolder(slnFile, fullProjectPath); + var fullProjectPaths = _arguments.Select(p => + { + var fullPath = Path.GetFullPath(p); + return Directory.Exists(fullPath) ? + MsbuildProject.GetProjectFileFromDirectory(fullPath).FullName : + fullPath; + }).ToList(); - slnFile.AddProject(fullProjectPath, solutionFolders); - } + var preAddProjectCount = slnFile.Projects.Count; - if (slnFile.Projects.Count > preAddProjectCount) - { - slnFile.Write(); - } + foreach (var fullProjectPath in fullProjectPaths) + { + // Identify the intended solution folders + var solutionFolders = DetermineSolutionFolder(slnFile, fullProjectPath); - return 0; + slnFile.AddProject(fullProjectPath, solutionFolders); } - private static IList GetSolutionFoldersFromProjectPath(string projectFilePath) + if (slnFile.Projects.Count > preAddProjectCount) { - var solutionFolders = new List(); + slnFile.Write(); + } - if (!IsPathInTreeRootedAtSolutionDirectory(projectFilePath)) - return solutionFolders; + return 0; + } - var currentDirString = $".{Path.DirectorySeparatorChar}"; - if (projectFilePath.StartsWith(currentDirString)) - { - projectFilePath = projectFilePath.Substring(currentDirString.Length); - } + private static List GetSolutionFoldersFromProjectPath(string projectFilePath) + { + var solutionFolders = new List(); - var projectDirectoryPath = TrimProject(projectFilePath); - if (string.IsNullOrEmpty(projectDirectoryPath)) - return solutionFolders; + if (!IsPathInTreeRootedAtSolutionDirectory(projectFilePath)) + return solutionFolders; - var solutionFoldersPath = TrimProjectDirectory(projectDirectoryPath); - if (string.IsNullOrEmpty(solutionFoldersPath)) - return solutionFolders; + var currentDirString = $".{Path.DirectorySeparatorChar}"; + if (projectFilePath.StartsWith(currentDirString)) + { + projectFilePath = projectFilePath.Substring(currentDirString.Length); + } - solutionFolders.AddRange(solutionFoldersPath.Split(Path.DirectorySeparatorChar)); + var projectDirectoryPath = Path.GetDirectoryName(projectFilePath); + if (string.IsNullOrEmpty(projectDirectoryPath)) + return solutionFolders; + var solutionFoldersPath = Path.GetDirectoryName(projectDirectoryPath); + if (string.IsNullOrEmpty(solutionFoldersPath)) return solutionFolders; - } - private IList DetermineSolutionFolder(SlnFile slnFile, string fullProjectPath) - { - if (_inRoot) - { - // The user requested all projects go to the root folder - return null; - } - - if (_relativeRootSolutionFolders != null) - { - // The user has specified an explicit root - return _relativeRootSolutionFolders; - } - - // We determine the root for each individual project - var relativeProjectPath = Path.GetRelativePath( - PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory), - fullProjectPath); - - return GetSolutionFoldersFromProjectPath(relativeProjectPath); - } + solutionFolders.AddRange(solutionFoldersPath.Split(Path.DirectorySeparatorChar)); - private static bool IsPathInTreeRootedAtSolutionDirectory(string path) - { - return !path.StartsWith(".."); - } + return solutionFolders; + } - private static string TrimProject(string path) + private IList DetermineSolutionFolder(SlnFile slnFile, string fullProjectPath) + { + if (_inRoot) { - return Path.GetDirectoryName(path); + // The user requested all projects go to the root folder + return null; } - private static string TrimProjectDirectory(string path) + if (_relativeRootSolutionFolders != null) { - return Path.GetDirectoryName(path); + // The user has specified an explicit root + return _relativeRootSolutionFolders; } + + // We determine the root for each individual project + var relativeProjectPath = Path.GetRelativePath( + PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory), + fullProjectPath); + + return GetSolutionFoldersFromProjectPath(relativeProjectPath); + } + + private static bool IsPathInTreeRootedAtSolutionDirectory(string path) + { + return !path.StartsWith(".."); } } diff --git a/src/Cli/dotnet/commands/dotnet-sln/add/SlnAddParser.cs b/src/Cli/dotnet/commands/dotnet-sln/add/SlnAddParser.cs index 2965bfc23800..686e1a9a7e1c 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/add/SlnAddParser.cs +++ b/src/Cli/dotnet/commands/dotnet-sln/add/SlnAddParser.cs @@ -5,45 +5,46 @@ using Microsoft.DotNet.Tools.Sln.Add; using LocalizableStrings = Microsoft.DotNet.Tools.Sln.LocalizableStrings; -namespace Microsoft.DotNet.Cli +namespace Microsoft.DotNet.Cli; + +public static class SlnAddParser { - public static class SlnAddParser + public static readonly CliArgument> ProjectPathArgument = new(LocalizableStrings.AddProjectPathArgumentName) + { + HelpName = LocalizableStrings.AddProjectPathArgumentName, + Description = LocalizableStrings.AddProjectPathArgumentDescription, + Arity = ArgumentArity.ZeroOrMore, + }; + + public static readonly CliOption InRootOption = new("--in-root") + { + Description = LocalizableStrings.AddProjectInRootArgumentDescription, + }; + + public static readonly CliOption SolutionFolderOption = new("--solution-folder", "-s") + { + Description = LocalizableStrings.AddProjectSolutionFolderArgumentDescription, + }; + + private static readonly CliCommand Command = ConstructCommand(); + + public static CliCommand GetCommand() + { + return Command; + } + + private static CliCommand ConstructCommand() { - public static readonly CliArgument> ProjectPathArgument = new(LocalizableStrings.AddProjectPathArgumentName) - { - HelpName = LocalizableStrings.AddProjectPathArgumentName, - Description = LocalizableStrings.AddProjectPathArgumentDescription, - Arity = ArgumentArity.ZeroOrMore, - }; - - public static readonly CliOption InRootOption = new("--in-root") - { - Description = LocalizableStrings.InRoot - }; - - public static readonly CliOption SolutionFolderOption = new("--solution-folder", "-s") - { - Description = LocalizableStrings.AddProjectSolutionFolderArgumentDescription - }; - - private static readonly CliCommand Command = ConstructCommand(); - - public static CliCommand GetCommand() - { - return Command; - } - - private static CliCommand ConstructCommand() - { - CliCommand command = new("add", LocalizableStrings.AddAppFullName); - - command.Arguments.Add(ProjectPathArgument); - command.Options.Add(InRootOption); - command.Options.Add(SolutionFolderOption); - - command.SetAction((parseResult) => new AddProjectToSolutionCommand(parseResult).Execute()); - - return command; - } + CliCommand command = new("add", LocalizableStrings.AddAppFullName); + + command.Subcommands.Add(SlnAddFileParser.GetCommand()); + command.Subcommands.Add(SlnAddFolderParser.GetCommand()); + command.Arguments.Add(ProjectPathArgument); + command.Options.Add(InRootOption); + command.Options.Add(SolutionFolderOption); + + command.SetAction((parseResult) => new AddProjectToSolutionCommand(parseResult).Execute()); + + return command; } } diff --git a/src/Cli/dotnet/commands/dotnet-sln/add/file/AddFileToSolutionCommand.cs b/src/Cli/dotnet/commands/dotnet-sln/add/file/AddFileToSolutionCommand.cs new file mode 100644 index 000000000000..90b55c6b8d72 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-sln/add/file/AddFileToSolutionCommand.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Sln.Internal; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Common; + +namespace Microsoft.DotNet.Tools.Sln.Add; + +internal class AddFileToSolutionCommand : CommandBase +{ + private readonly string _fileOrDirectory; + private readonly bool _inRoot; + private readonly IList _relativeRootSolutionFolders; + private readonly IReadOnlyList _arguments; + + public AddFileToSolutionCommand(ParseResult parseResult) : base(parseResult) + { + var projectPaths = parseResult.GetValue(SlnAddParser.ProjectPathArgument)?.ToArray() ?? []; + if (projectPaths.Length != 0) + { + throw new GracefulException(LocalizableStrings.ProjectPathArgumentShouldNotBeProvidedForDotnetSlnAddFile); + } + + _fileOrDirectory = parseResult.GetValue(SlnCommandParser.SlnArgument); + _arguments = parseResult.GetValue(SlnAddFileParser.FilePathArgument).ToArray(); + _inRoot = parseResult.GetValue(SlnAddFileParser.InRootOption); + string relativeRoot = parseResult.GetValue(SlnAddFileParser.SolutionFolderOption); + + SlnArgumentValidator.ParseAndValidateArguments(_arguments, SlnArgumentValidator.CommandType.Add, _inRoot, relativeRoot, subcommand: "file"); + + if (string.IsNullOrEmpty(relativeRoot)) + { + _relativeRootSolutionFolders = null; + } + else + { + relativeRoot = PathUtility.GetPathWithDirectorySeparator(relativeRoot); + _relativeRootSolutionFolders = relativeRoot.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); + } + } + + public override int Execute() + { + SlnFile slnFile = SlnFileFactory.CreateFromFileOrDirectory(_fileOrDirectory); + + var arguments = _parseResult.GetValue(SlnAddFileParser.FilePathArgument).ToArray(); + if (arguments.Length == 0) + { + throw new GracefulException(LocalizableStrings.SpecifyAtLeastOneFileToAdd); + } + + PathUtility.EnsureAllPathsExist(arguments, LocalizableStrings.CouldNotFindFile, allowDirectories: false); + + var relativeFilePaths = _arguments.Select(f => + { + var fullFilePath = Path.GetFullPath(f); + return Path.GetRelativePath( + PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory), + fullFilePath); + }).ToList(); + + // Perform the same validations as Visual Studio: + // 1. A solution cannot be added as a solution item to itself. + // 2. An existing project cannot be added as a solution item. + // 3. An existing solution item cannot be added as a solution item. + var solutionFileName = Path.GetFileName(slnFile.FullPath); + foreach (var relativeFilePath in relativeFilePaths) + { + if (StringComparer.OrdinalIgnoreCase.Equals(solutionFileName, relativeFilePath)) + { + throw new GracefulException(LocalizableStrings.CannotAddTheSameSolutionToItself); + } + + foreach (var project in slnFile.Projects) + { + if (StringComparer.OrdinalIgnoreCase.Equals(relativeFilePath, project.FilePath)) + { + throw new GracefulException(LocalizableStrings.CannotAddExistingProjectAsSolutionItem); + } + + if (project.GetSolutionItemsSectionOrDefault() is not SlnSection solutionItems) { continue; } + Dictionary solutionItemsDictionary = new(solutionItems.GetContent(), StringComparer.OrdinalIgnoreCase); + if (solutionItemsDictionary.ContainsKey(relativeFilePath)) + { + throw new GracefulException(string.Format(LocalizableStrings.SolutionItemWithTheSameNameExists, relativeFilePath, project.Name)); + } + } + } + + var preAddSolutionItemsCount = slnFile.Projects + .Sum(p => p.GetSolutionItemsSectionOrDefault() is { } solutionItems ? solutionItems.GetContent().Count() : 0); + + var solutionFolders = _relativeRootSolutionFolders ?? ["Solution Items"]; + + foreach (var relativeFilePath in relativeFilePaths) + { + foreach (var solutionFolder in solutionFolders) + { + if (slnFile.SolutionFolderContainsSolutionItem(solutionFolder, relativeFilePath)) + { + throw new GracefulException(string.Format(LocalizableStrings.SolutionItemWithTheSameNameExists, relativeFilePath, solutionFolder)); + } + } + + slnFile.AddSolutionItem(relativeFilePath, solutionFolders); + } + + var postAddSolutionItemsCount = slnFile.Projects + .Sum(p => p.GetSolutionItemsSectionOrDefault() is { } solutionItems ? solutionItems.GetContent().Count() : 0); + if (postAddSolutionItemsCount > preAddSolutionItemsCount) + { + slnFile.Write(); + } + + return 0; + } +} diff --git a/src/Cli/dotnet/commands/dotnet-sln/add/file/SlnAddFileParser.cs b/src/Cli/dotnet/commands/dotnet-sln/add/file/SlnAddFileParser.cs new file mode 100644 index 000000000000..e52decc360dd --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-sln/add/file/SlnAddFileParser.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using Microsoft.DotNet.Tools.Sln.Add; +using LocalizableStrings = Microsoft.DotNet.Tools.Sln.LocalizableStrings; + +namespace Microsoft.DotNet.Cli; + +public static class SlnAddFileParser +{ + public static readonly CliArgument> FilePathArgument = new(LocalizableStrings.AddFilePathArgumentName) + { + HelpName = LocalizableStrings.AddFilePathArgumentName, + Description = LocalizableStrings.AddFilePathArgumentDescription, + Arity = ArgumentArity.OneOrMore, + }; + + public static readonly CliOption InRootOption = new("--in-root") + { + Description = LocalizableStrings.AddFileInRootArgumentDescription, + }; + + public static readonly CliOption SolutionFolderOption = new("--solution-folder", "-s") + { + Description = LocalizableStrings.AddFileSolutionFolderArgumentDescription, + }; + + private static readonly CliCommand Command = ConstructCommand(); + + public static CliCommand GetCommand() => Command; + + private static CliCommand ConstructCommand() + { + CliCommand command = new("file", LocalizableStrings.AddFileFullName); + + command.Arguments.Add(FilePathArgument); + command.Options.Add(InRootOption); + command.Options.Add(SolutionFolderOption); + + command.SetAction((parseResult) => new AddFileToSolutionCommand(parseResult).Execute()); + + return command; + } +} diff --git a/src/Cli/dotnet/commands/dotnet-sln/add/folder/AddFolderToSolutionCommand.cs b/src/Cli/dotnet/commands/dotnet-sln/add/folder/AddFolderToSolutionCommand.cs new file mode 100644 index 000000000000..22c995180725 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-sln/add/folder/AddFolderToSolutionCommand.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Sln.Internal; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Common; + +namespace Microsoft.DotNet.Tools.Sln.Add; + +internal class AddFolderToSolutionCommand : CommandBase +{ + private readonly string _fileOrDirectory; + private readonly bool _inRoot; + private readonly IList _relativeRootSolutionFolders; + private readonly IReadOnlyList _arguments; + + public AddFolderToSolutionCommand(ParseResult parseResult) : base(parseResult) + { + var projectPaths = parseResult.GetValue(SlnAddParser.ProjectPathArgument)?.ToArray() ?? []; + if (projectPaths.Length != 0) + { + throw new GracefulException(LocalizableStrings.ProjectPathArgumentShouldNotBeProvidedForDotnetSlnAddFolder); + } + + _fileOrDirectory = parseResult.GetValue(SlnCommandParser.SlnArgument); + _arguments = parseResult.GetValue(SlnAddFolderParser.FolderPathArgument).ToArray(); + _inRoot = parseResult.GetValue(SlnAddFolderParser.InRootOption); + string relativeRoot = parseResult.GetValue(SlnAddFolderParser.SolutionFolderOption); + + SlnArgumentValidator.ParseAndValidateArguments(_arguments, SlnArgumentValidator.CommandType.Add, _inRoot, relativeRoot, subcommand: "folder"); + + if (string.IsNullOrEmpty(relativeRoot)) + { + _relativeRootSolutionFolders = null; + } + else + { + relativeRoot = PathUtility.GetPathWithDirectorySeparator(relativeRoot); + _relativeRootSolutionFolders = relativeRoot.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); + } + } + + public override int Execute() + { + SlnFile slnFile = SlnFileFactory.CreateFromFileOrDirectory(_fileOrDirectory); + + if (_arguments.Count == 0) + { + throw new GracefulException(Tools.Sln.LocalizableStrings.SpecifyAtLeastOneFolderToAdd); + } + + // Visual Studio doesn't allow certain solution folder names + if (!_arguments.All(SlnArgumentValidator.IsValidSolutionFolderName)) + { + throw new GracefulException(Tools.Sln.LocalizableStrings.SolutionFolderNameCannot); + } + + var fullSolutionFolderPaths = _arguments.Select(Path.GetFullPath).ToList(); + + var preAddProjectCount = slnFile.Projects.Count; + + foreach (var fullSolutionFolderPath in fullSolutionFolderPaths) + { + var solutionFolders = DetermineSolutionFolder(slnFile, fullSolutionFolderPath); + slnFile.AddSolutionFolder(fullSolutionFolderPath, solutionFolders); + } + + if (slnFile.Projects.Count > preAddProjectCount) + { + slnFile.Write(); + } + + return 0; + } + + private static List GetSolutionFoldersFromProjectPath(string projectFilePath) + { + List solutionFolders = []; + + if (!IsPathInTreeRootedAtSolutionDirectory(projectFilePath)) + return solutionFolders; + + var currentDirString = $".{Path.DirectorySeparatorChar}"; + if (projectFilePath.StartsWith(currentDirString)) + { + projectFilePath = projectFilePath.Substring(currentDirString.Length); + } + + var projectDirectoryPath = Path.GetDirectoryName(projectFilePath); + if (string.IsNullOrEmpty(projectDirectoryPath)) + return solutionFolders; + + var solutionFoldersPath = Path.GetDirectoryName(projectDirectoryPath); + if (string.IsNullOrEmpty(solutionFoldersPath)) + return solutionFolders; + + solutionFolders.AddRange(solutionFoldersPath.Split(Path.DirectorySeparatorChar)); + + return solutionFolders; + } + + private IList DetermineSolutionFolder(SlnFile slnFile, string fullProjectPath) + { + if (_inRoot) + { + // The user requested all projects go to the root folder + return null; + } + + if (_relativeRootSolutionFolders != null) + { + // The user has specified an explicit root + return _relativeRootSolutionFolders; + } + + // We determine the root for each individual project + var relativeProjectPath = Path.GetRelativePath( + PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory), + fullProjectPath); + + return GetSolutionFoldersFromProjectPath(relativeProjectPath); + } + + private static bool IsPathInTreeRootedAtSolutionDirectory(string path) => !path.StartsWith(".."); +} diff --git a/src/Cli/dotnet/commands/dotnet-sln/add/folder/SlnAddFolderParser.cs b/src/Cli/dotnet/commands/dotnet-sln/add/folder/SlnAddFolderParser.cs new file mode 100644 index 000000000000..230dad954f60 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-sln/add/folder/SlnAddFolderParser.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using Microsoft.DotNet.Tools.Sln.Add; +using LocalizableStrings = Microsoft.DotNet.Tools.Sln.LocalizableStrings; + +namespace Microsoft.DotNet.Cli; + +public static class SlnAddFolderParser +{ + public static readonly CliArgument> FolderPathArgument = new(LocalizableStrings.AddFolderPathArgumentName) + { + HelpName = LocalizableStrings.AddFolderPathArgumentName, + Description = LocalizableStrings.AddFolderPathArgumentDescription, + Arity = ArgumentArity.OneOrMore, + }; + + public static readonly CliOption InRootOption = new("--in-root") + { + Description = LocalizableStrings.AddFolderInRootArgumentDescription, + }; + + public static readonly CliOption SolutionFolderOption = new("--solution-folder", "-s") + { + Description = LocalizableStrings.AddFolderSolutionFolderArgumentDescription, + }; + + private static readonly CliCommand Command = ConstructCommand(); + + public static CliCommand GetCommand() => Command; + + private static CliCommand ConstructCommand() + { + CliCommand command = new("folder", LocalizableStrings.AddFolderFullName); + + command.Arguments.Add(FolderPathArgument); + command.Options.Add(InRootOption); + command.Options.Add(SolutionFolderOption); + + command.SetAction((parseResult) => new AddFolderToSolutionCommand(parseResult).Execute()); + + return command; + } +} diff --git a/src/Cli/dotnet/commands/dotnet-sln/remove/Program.cs b/src/Cli/dotnet/commands/dotnet-sln/remove/Program.cs index 1e4ce2e23b95..b33baabcab02 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/remove/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-sln/remove/Program.cs @@ -11,7 +11,7 @@ namespace Microsoft.DotNet.Tools.Sln.Remove internal class RemoveProjectFromSolutionCommand : CommandBase { private readonly string _fileOrDirectory; - private readonly IReadOnlyCollection _arguments; + private readonly IReadOnlyList _arguments; public RemoveProjectFromSolutionCommand(ParseResult parseResult) : base(parseResult) { @@ -19,7 +19,7 @@ public RemoveProjectFromSolutionCommand(ParseResult parseResult) : base(parseRes _arguments = (parseResult.GetValue(SlnRemoveParser.ProjectPathArgument) ?? Array.Empty()).ToList().AsReadOnly(); - SlnArgumentValidator.ParseAndValidateArguments(_fileOrDirectory, _arguments, SlnArgumentValidator.CommandType.Remove); + SlnArgumentValidator.ParseAndValidateArguments(_arguments, SlnArgumentValidator.CommandType.Remove); } public override int Execute() diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.cs.xlf index f5b32233e7d0..54a33358b977 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.cs.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.cs.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. Cílová cesta složky řešení, do které chcete přidat projekty @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - Umístěte projekt do kořene řešení, není potřeba vytvářet složku řešení. + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. Odebere ze souboru řešení jeden nebo více projektů. @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ Projekty + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. Parametry --solution-folder a --in-root nejdou použít společně; použijte jenom jeden z nich. @@ -122,6 +217,41 @@ Složk(a/y) řešení + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.de.xlf index f8eb231f7950..f9d61f238f06 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.de.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.de.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. Der Zielpfad des Projektmappenordners, dem die Projekte hinzugefügt werden sollen. @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - Platzieren Sie das Projekt im Stamm der Projektmappe, statt einen Projektmappenordner zu erstellen. + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. Entfernt ein oder mehrere Projekte von einer Projektmappendatei. @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ Projekt(e) + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. Die Optionen "--solution-folder" und "--in-root" können nicht zusammen verwendet werden; verwenden Sie nur eine der Optionen. @@ -122,6 +217,41 @@ Projektmappenordner + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.es.xlf index 6ed23a5e39fe..938fe162cd18 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.es.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. Ruta de acceso de la carpeta de la solución de destino a la que agregar los proyectos. @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - Coloque el proyecto en la raíz de la solución, en lugar de crear una carpeta de soluciones. + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. Quita uno o varios proyectos de un archivo de solución. @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ Proyectos + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. Las opciones --in-root y --solution-folder no se pueden usar juntas. Utilice solo una de ellas. @@ -122,6 +217,41 @@ Carpeta(s) de solución(es) + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.fr.xlf index b1d8c5880aff..0ceb1c2477f4 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.fr.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.fr.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. Chemin de dossier solution de destination où les projets doivent être ajoutés. @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - Place le projet à la racine de la solution, au lieu de créer un dossier solution. + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. Supprimez un ou plusieurs projets d'un fichier solution. @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ Projet(s) + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. N'utilisez pas en même temps les options --solution-folder et --in-root. Utilisez uniquement l'une des deux options. @@ -122,6 +217,41 @@ Dossier(s) de solutions + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.it.xlf index 989a4f33844e..17ed72a64be0 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.it.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.it.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. Percorso della cartella della soluzione di destinazione in cui aggiungere i progetti. @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - Inserisce il progetto nella radice della soluzione invece di creare una cartella soluzione. + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. Consente di rimuovere uno o più progetti da un file di soluzione. @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ Progetto/i + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. Non è possibile usare contemporaneamente le opzioni --solution-folder e --in-root. Usare una sola delle opzioni. @@ -122,6 +217,41 @@ Cartelle della soluzione + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ja.xlf index 2683028f11aa..2e6daabb600b 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ja.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ja.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. プロジェクトの追加先のソリューション フォルダー パス。 @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - ソリューション フォルダーを作成するのではなく、プロジェクトをソリューションのルートに配置します。 + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. 1 つ以上のプロジェクトをソリューション ファイルから削除します。 @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ プロジェクト + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. --solution-folder オプションと --in-root オプションを一緒に使用することはできません。いずれかのオプションだけを使用します。 @@ -122,6 +217,41 @@ ソリューション フォルダー + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ko.xlf index 895400338f29..db0f8f9f3062 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ko.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ko.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. 프로젝트를 추가하려는 대상 솔루션 폴더 경로입니다. @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - 솔루션 폴더를 만드는 대신, 솔루션의 루트에 프로젝트를 배치하세요. + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. 솔루션 파일에서 하나 이상의 프로젝트를 제거합니다. @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ 프로젝트 + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. --solution-folder와 --in-root 옵션을 함께 사용할 수 없습니다. 옵션을 하나만 사용하세요. @@ -122,6 +217,41 @@ 솔루션 폴더 + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.pl.xlf index e384d5d00475..5fef97b1445d 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.pl.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.pl.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. Ścieżka folderu rozwiązania docelowego określająca lokalizację dodawanych projektów. @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - Umieść projekt w katalogu głównym rozwiązania zamiast tworzyć folder rozwiązania. + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. Usuń co najmniej jeden projekt z pliku rozwiązania. @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ Projekty + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. Opcji --solution-folder i --in-root nie można używać razem; użyj tylko jednej z tych opcji. @@ -122,6 +217,41 @@ Foldery rozwiązań + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.pt-BR.xlf index ba4d879b8340..866179d05c3b 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.pt-BR.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. O caminho da pasta de solução de destino ao qual adicionar os projetos. @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - Coloque o projeto na raiz da solução, em vez de criar uma pasta da solução. + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. Remover um ou mais projetos de um arquivo de solução. @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ Projetos + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. As opções --solution-folder e --in-root não podem ser usadas juntas. Use somente uma das opções. @@ -122,6 +217,41 @@ Pastas da Solução + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ru.xlf index c5dfbd52a60c..e9fb1b16f2b7 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ru.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.ru.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. Путь к папке назначения решения, в которую будут добавлены проекты. @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - Поместите проект в корень решения вместо создания папки решения. + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. Удаление проектов из файла решения. @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ Проекты + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. Параметры --solution-folder и --in-root options не могут быть использованы одновременно; оставьте только один из параметров. @@ -122,6 +217,41 @@ Папки решения + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.tr.xlf index fea7bcc4ea34..2decc21b64ac 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.tr.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.tr.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. Projelerin ekleneceği hedef çözüm klasör yolu. @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - Bir çözüm klasörü oluşturmak yerine projeyi çözümün köküne yerleştirin. + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. Bir çözüm dosyasından bir veya daha fazla projeyi kaldırır. @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ Projeler + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. --solution-folder ve --in-root seçenekleri birlikte kullanılamaz; seçeneklerden yalnızca birini kullanın. @@ -122,6 +217,41 @@ Çözüm Klasörleri + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.zh-Hans.xlf index 82241d2d4a6a..ba7de6550d24 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.zh-Hans.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. 要添加项目的目标解决方案文件夹路径。 @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - 将项目放在解决方案的根目录下,而不是创建解决方案文件夹。 + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. 从解决方案文件中删除一个或多个项目。 @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ 项目 + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. --solution-folder 和 --in-root 选项不能一起使用;请仅使用其中一个选项。 @@ -122,6 +217,41 @@ 解决方案文件夹 + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.zh-Hant.xlf index 5b061ec4f5f2..bc15c53e42a5 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/commands/dotnet-sln/xlf/LocalizableStrings.zh-Hant.xlf @@ -2,6 +2,61 @@ + + Add one or more solution items to a solution file. + Add one or more solution items to a solution file. + + + + Place file in root of the solution, rather than creating a solution folder. + Place file in root of the solution, rather than creating a solution folder. + + + + The paths to the solution items to add to the solution. + The paths to the solution items to add to the solution. + + + + FILE_PATH + FILE_PATH + + + + The destination solution folder path to add the files to. + The destination solution folder path to add the files to. + + + + Add one or more solution folders to a solution file. + Add one or more solution folders to a solution file. + + + + Place folder in root of the solution, rather than creating a solution folder. + Place folder in root of the solution, rather than creating a solution folder. + + + + The paths to the solution folders to add to the solution. + The paths to the solution folders to add to the solution. + + + + FOLDER_PATH + FOLDER_PATH + + + + The destination solution folder path to add the folders to. + The destination solution folder path to add the folders to. + + + + Place project in root of the solution, rather than creating a solution folder. + Place project in root of the solution, rather than creating a solution folder. + + The destination solution folder path to add the projects to. 要新增專案的目標目的地解決方案資料夾路徑。 @@ -32,9 +87,19 @@ Only .sln files can be migrated to .slnx format. - - Place project in root of the solution, rather than creating a solution folder. - 請將專案放置在解決方案的根目錄中,而非放置於建立解決方案的資料夾中。 + + An existing project cannot be added as a solution item. + An existing project cannot be added as a solution item. + + + + Cannot add the same solution to itself. + Cannot add the same solution to itself. + + + + Could not find file `{0}`. + Could not find file `{0}`. @@ -47,6 +112,16 @@ Generate a .slnx file from a .sln file. + + PROJECT_PATH should not be provided for `dotnet sln add file` + PROJECT_PATH should not be provided for `dotnet sln add file` + + + + PROJECT_PATH should not be provided for `dotnet sln add folder` + PROJECT_PATH should not be provided for `dotnet sln add folder` + + Remove one or more projects from a solution file. 從解決方案檔移除一或多個專案。 @@ -77,6 +152,16 @@ .slnx file {0} generated. + + The solution {0} already contains the solution item `{1}` + The solution {0} already contains the solution item `{1}` + + + + The solution {0} already contains the solution folder `{1}` + The solution {0} already contains the solution folder `{1}` + + SLN_FILE SLN_FILE @@ -112,6 +197,16 @@ 專案 + + The type of the solution element to add or remove. Allowed values are project, item, and folder. + The type of the solution element to add or remove. Allowed values are project, item, and folder. + + + + The solution folder `{0}` was added to the solution + The solution folder `{0}` was added to the solution + + The --solution-folder and --in-root options cannot be used together; use only one of the options. 不可同時使用 --solution-folder 和 --in-root 選項; 請只使用其中一個選項。 @@ -122,6 +217,41 @@ 解決方案資料夾 + + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + Solution Folder names cannot: +- contain any of the following characters: / : ? \ * " < > | +- contain Unicode control characters +- contain surrogate characters +- be system reserved names, including CON, AUX, PRN, COM1 or LPT2 +- be . or .. + + + + The solution item `{0}` was added to the solution folder `{1}` + The solution item `{0}` was added to the solution folder `{1}` + + + + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + There is already an existing solution item '{0}' with the same name in the solution folder '{1}'. + + + + You must specify at least one file to add. + You must specify at least one file to add. + + + + You must specify at least one folder to add. + You must specify at least one folder to add. + + \ No newline at end of file diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.cs.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.cs.xlf index 8ae7960c4016..6c476ad1005a 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.cs.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.cs.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" Soubor projektu + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference Odkaz @@ -804,11 +809,6 @@ Výchozí hodnota je false, ale pokud cílíte na .NET 7 nebo nižší a je zad Neobnoví projekt před sestavením. - - Project `{0}` removed from the solution. - Projekt {0} byl z řešení odebrán. - - Invalid XML: {0} Neplatné XML: {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.de.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.de.xlf index e8aed6ea1205..880fb3d8b32c 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.de.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.de.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" Projektdatei + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference Verweis @@ -804,11 +809,6 @@ Der Standardwert lautet FALSE. Wenn sie jedoch auf .NET 7 oder niedriger abziele Hiermit wird das Projekt nicht vor der Erstellung wiederhergestellt. - - Project `{0}` removed from the solution. - Das Projekt "{0}" wurde aus der Projektmappe entfernt. - - Invalid XML: {0} Ungültiges XML: {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.es.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.es.xlf index d0456889ad38..4411aa857ce2 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.es.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.es.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" Archivo de proyecto + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference Referencia @@ -805,11 +810,6 @@ El valor predeterminado es "false". Sin embargo, cuando el destino es .NET 7 o i No restaure el proyecto antes de la compilación. - - Project `{0}` removed from the solution. - Se ha quitado el proyecto "{0}" de la solución. - - Invalid XML: {0} XML no válido: {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.fr.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.fr.xlf index da5f2b48c79d..b378976bd8a2 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.fr.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.fr.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" Fichier projet + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference Référence @@ -805,11 +810,6 @@ La valeur par défaut est « false ». Toutefois, lorsque vous ciblez .NET 7 ou Ne restaurez pas le projet avant la génération. - - Project `{0}` removed from the solution. - Projet '{0}' retiré de la solution. - - Invalid XML: {0} XML non valide : {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.it.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.it.xlf index 84b8e63f7b0d..2a0284f22932 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.it.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.it.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" File di progetto + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference Riferimento @@ -804,11 +809,6 @@ Il valore predefinito è 'false'. Tuttavia, quando la destinazione è .NET 7 o u Non ripristina il progetto prima della compilazione. - - Project `{0}` removed from the solution. - Il progetto `{0}` è stato rimosso dalla soluzione. - - Invalid XML: {0} XML non valido: {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.ja.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.ja.xlf index 955e9cfc91e8..4820b0af393d 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.ja.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.ja.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" プロジェクト ファイル + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference 参照 @@ -804,11 +809,6 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is ビルドする前にプロジェクトを復元しません。 - - Project `{0}` removed from the solution. - プロジェクト `{0}` がソリューションから削除されました。 - - Invalid XML: {0} XML が無効です: {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.ko.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.ko.xlf index d26185296366..c689b8e77d1d 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.ko.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.ko.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" 프로젝트 파일 + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference 참조 @@ -805,11 +810,6 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is 빌드하기 전에 프로젝트를 복원하지 마세요. - - Project `{0}` removed from the solution. - '{0}' 프로젝트가 솔루션에서 제거되었습니다. - - Invalid XML: {0} 잘못된 XML: {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.pl.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.pl.xlf index 091c83288270..4ddb8ecfff81 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.pl.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.pl.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" Plik projektu + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference Odwołanie @@ -804,11 +809,6 @@ Wartość domyślna to „false”. Jednak w przypadku określania wartości doc Nie przywracaj projektu przed kompilowaniem. - - Project `{0}` removed from the solution. - Projekt „{0}” został skasowany z rozwiązania. - - Invalid XML: {0} Nieprawidłowy kod XML: {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf index 2a82fe3a1562..2d0803c967ae 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" Arquivo de Projeto + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference Referência @@ -804,11 +809,6 @@ O padrão é falso.' No entanto, ao direcionar o .NET 7 ou inferior, o padrão Não restaurar o projeto antes de compilar. - - Project `{0}` removed from the solution. - O projeto `{0}` foi removido da solução. - - Invalid XML: {0} XML inválido: {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.ru.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.ru.xlf index 30524ccfe60c..b743ada52f8f 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.ru.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.ru.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" Файл проекта + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference Ссылка @@ -804,11 +809,6 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is Не восстанавливать проект перед сборкой. - - Project `{0}` removed from the solution. - Проект "{0}" удален из решения. - - Invalid XML: {0} Недопустимый XML: {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.tr.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.tr.xlf index 2cd50917f9fc..1c22b0537040 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.tr.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.tr.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" Proje dosyası + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference Başvuru @@ -804,11 +809,6 @@ Varsayılan değer 'false' olur. Ancak çalışma zamanı tanımlayıcısı beli Projeyi derlemeden önce geri yüklemeyin. - - Project `{0}` removed from the solution. - `{0}` projesi çözümden kaldırıldı. - - Invalid XML: {0} Geçersiz XML: {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf index 2345a2ae6ecd..b8ee97edfc17 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" 项目文件 + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference 引用 @@ -804,11 +809,6 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is 生成前请勿还原项目。 - - Project `{0}` removed from the solution. - 已从解决方案中移除项目“{0}”。 - - Invalid XML: {0} 无效的 XML: {0} diff --git a/src/Cli/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf index aff0bb272f7f..fbf12388ba6e 100644 --- a/src/Cli/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf @@ -175,6 +175,11 @@ export PATH="$PATH:{0}" 專案檔 + + Project `{0}` removed from the solution. + Project `{0}` removed from the solution. + + Reference 參考 @@ -804,11 +809,6 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is 建置前請勿還原該專案。 - - Project `{0}` removed from the solution. - 專案 `{0}` 已從解決方案移除。 - - Invalid XML: {0} XML 無效: {0} diff --git a/test/TestAssets/TestProjects/InvalidSolution/README b/test/TestAssets/TestProjects/InvalidSolution/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/InvalidSolution/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/SlnFileWithSolutionItemsInNestedFolders/TextFile1.txt b/test/TestAssets/TestProjects/SlnFileWithSolutionItemsInNestedFolders/TextFile1.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/TestAssets/TestProjects/TestAppWithEmptySln/Empty/README b/test/TestAssets/TestProjects/TestAppWithEmptySln/Empty/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithEmptySln/Empty/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithEmptySln/README b/test/TestAssets/TestProjects/TestAppWithEmptySln/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithEmptySln/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/README b/test/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndCaseSensitiveSolutionFolders/src/App/README b/test/TestAssets/TestProjects/TestAppWithSlnAndCaseSensitiveSolutionFolders/src/App/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndCaseSensitiveSolutionFolders/src/App/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/README b/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojInSubDirVSErrors/Base/Second/Third/README b/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojInSubDirVSErrors/Base/Second/Third/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojInSubDirVSErrors/Base/Second/Third/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/Empty/README b/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/Empty/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/Empty/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/README b/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/README b/test/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferencesWithEscapedDirSep/README b/test/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferencesWithEscapedDirSep/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferencesWithEscapedDirSep/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/App.csproj b/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/App.csproj new file mode 100644 index 000000000000..e15eb5eaa4d0 --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/App.csproj @@ -0,0 +1,9 @@ + + + + + Exe + $(CurrentTargetFramework) + + + diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/App.sln b/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/App.sln new file mode 100644 index 000000000000..6dd8a937470a --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/App.sln @@ -0,0 +1,33 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "App.csproj", "{7072A694-548F-4CAE-A58F-12D257D5F486}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/Program.cs b/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/Program.cs new file mode 100644 index 000000000000..11f138d4d08b --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/Program.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +class Program +{ + static void Main(string[] args) + { + Console.WriteLine("Hello from the main app"); + } +} diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/src/Lib/README b/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/src/Lib/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/src/Lib/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/src/README b/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/src/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnAndReadmeInSubDir/src/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/App.csproj b/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/App.csproj new file mode 100644 index 000000000000..e15eb5eaa4d0 --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/App.csproj @@ -0,0 +1,9 @@ + + + + + Exe + $(CurrentTargetFramework) + + + diff --git a/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/App.sln b/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/App.sln new file mode 100644 index 000000000000..398f7afe0efa --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/App.sln @@ -0,0 +1,43 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "App.csproj", "{7072A694-548F-4CAE-A58F-12D257D5F486}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestFolder", "TestFolder", "{20BBBE75-F71E-4F09-8052-BB7738E4D289}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InnerTestFolder", "InnerTestFolder", "{CD94A576-E0D6-428D-924E-C91D6E1A924D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InnermostTestFolder", "InnermostTestFolder", "{9D7617ED-D4CF-4636-AB4D-9B68B1A18164}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {CD94A576-E0D6-428D-924E-C91D6E1A924D} = {20BBBE75-F71E-4F09-8052-BB7738E4D289} + {9D7617ED-D4CF-4636-AB4D-9B68B1A18164} = {CD94A576-E0D6-428D-924E-C91D6E1A924D} + EndGlobalSection +EndGlobal diff --git a/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/Program.cs b/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/Program.cs new file mode 100644 index 000000000000..11f138d4d08b --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/Program.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +class Program +{ + static void Main(string[] args) + { + Console.WriteLine("Hello from the main app"); + } +} diff --git a/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/src/Lib/README b/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/src/Lib/README new file mode 100644 index 000000000000..80a6229fc33f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithSlnContainingNestedSolutionFolder/src/Lib/README @@ -0,0 +1 @@ +This directory is intentionally empty. diff --git a/test/dotnet-sln.Tests/GivenDotnetSlnAdd.cs b/test/dotnet-sln.Tests/GivenDotnetSlnAdd.cs deleted file mode 100644 index 8c13d746f746..000000000000 --- a/test/dotnet-sln.Tests/GivenDotnetSlnAdd.cs +++ /dev/null @@ -1,1453 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.DotNet.Cli.Sln.Internal; -using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.Tools; -using Microsoft.DotNet.Tools.Common; - -namespace Microsoft.DotNet.Cli.Sln.Add.Tests -{ - public class GivenDotnetSlnAdd : SdkTest - { - private Func HelpText = (defaultVal) => $@"Description: - Add one or more projects to a solution file. - -Usage: - dotnet solution add [...] [options] - -Arguments: - The solution file to operate on. If not specified, the command will search the current directory for one. [default: {PathUtility.EnsureTrailingSlash(defaultVal)}] - The paths to the projects to add to the solution. - -Options: - --in-root Place project in root of the solution, rather than creating a solution folder. - -s, --solution-folder The destination solution folder path to add the projects to. - -?, -h, --help Show command line help"; - - public GivenDotnetSlnAdd(ITestOutputHelper log) : base(log) - { - } - - private const string ExpectedSlnFileAfterAddingLibProj = @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26006.2 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App\App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" -EndProject -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""Lib"", ""Lib\Lib.csproj"", ""__LIB_PROJECT_GUID__"" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 - __LIB_PROJECT_GUID__.Debug|Any CPU.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|Any CPU.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Release|Any CPU.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|Any CPU.Build.0 = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal -"; - - private const string ExpectedSlnFileAfterAddingLibProjToEmptySln = @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26006.2 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""Lib"", ""Lib\Lib.csproj"", ""__LIB_PROJECT_GUID__"" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - __LIB_PROJECT_GUID__.Debug|Any CPU.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|Any CPU.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Release|Any CPU.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|Any CPU.Build.0 = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal -"; - - private const string ExpectedSlnFileAfterAddingNestedProj = @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26006.2 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" -EndProject -Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""src"", ""src"", ""__SRC_FOLDER_GUID__"" -EndProject -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""Lib"", ""src\Lib\Lib.csproj"", ""__LIB_PROJECT_GUID__"" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 - __LIB_PROJECT_GUID__.Debug|Any CPU.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|Any CPU.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Release|Any CPU.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|Any CPU.Build.0 = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - __LIB_PROJECT_GUID__ = __SRC_FOLDER_GUID__ - EndGlobalSection -EndGlobal -"; - - private const string ExpectedSlnFileAfterAddingProjectWithoutMatchingConfigs = @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26006.2 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ProjectWithoutMatchingConfigs"", ""ProjectWithoutMatchingConfigs\ProjectWithoutMatchingConfigs.csproj"", ""{C49B64DE-4401-4825-8A88-10DCB5950E57}"" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - Foo Bar|Any CPU = Foo Bar|Any CPU - Foo Bar|x64 = Foo Bar|x64 - Foo Bar|x86 = Foo Bar|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x64.ActiveCfg = Debug|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x64.Build.0 = Debug|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x86.ActiveCfg = Debug|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x86.Build.0 = Debug|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|Any CPU.Build.0 = Release|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x64.ActiveCfg = Release|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x64.Build.0 = Release|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x86.ActiveCfg = Release|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x86.Build.0 = Release|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|Any CPU.ActiveCfg = Debug|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|Any CPU.Build.0 = Debug|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x64.ActiveCfg = Debug|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x64.Build.0 = Debug|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x86.ActiveCfg = Debug|Any CPU - {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x86.Build.0 = Debug|Any CPU - EndGlobalSection -EndGlobal -"; - - private const string ExpectedSlnFileAfterAddingProjectWithMatchingConfigs = @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26006.2 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ProjectWithMatchingConfigs"", ""ProjectWithMatchingConfigs\ProjectWithMatchingConfigs.csproj"", ""{C9601CA2-DB64-4FB6-B463-368C7764BF0D}"" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - Foo Bar|Any CPU = Foo Bar|Any CPU - Foo Bar|x64 = Foo Bar|x64 - Foo Bar|x86 = Foo Bar|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x64.ActiveCfg = Debug|x64 - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x64.Build.0 = Debug|x64 - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x86.ActiveCfg = Debug|x86 - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x86.Build.0 = Debug|x86 - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|Any CPU.Build.0 = Release|Any CPU - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x64.ActiveCfg = Release|x64 - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x64.Build.0 = Release|x64 - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x86.ActiveCfg = Release|x86 - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x86.Build.0 = Release|x86 - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|Any CPU.ActiveCfg = FooBar|Any CPU - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|Any CPU.Build.0 = FooBar|Any CPU - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x64.ActiveCfg = FooBar|x64 - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x64.Build.0 = FooBar|x64 - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x86.ActiveCfg = FooBar|x86 - {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x86.Build.0 = FooBar|x86 - EndGlobalSection -EndGlobal -"; - - private const string ExpectedSlnFileAfterAddingProjectWithAdditionalConfigs = @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26006.2 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ProjectWithAdditionalConfigs"", ""ProjectWithAdditionalConfigs\ProjectWithAdditionalConfigs.csproj"", ""{A302325B-D680-4C0E-8680-7AE283981624}"" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - Foo Bar|Any CPU = Foo Bar|Any CPU - Foo Bar|x64 = Foo Bar|x64 - Foo Bar|x86 = Foo Bar|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A302325B-D680-4C0E-8680-7AE283981624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A302325B-D680-4C0E-8680-7AE283981624}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x64.ActiveCfg = Debug|x64 - {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x64.Build.0 = Debug|x64 - {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x86.ActiveCfg = Debug|x86 - {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x86.Build.0 = Debug|x86 - {A302325B-D680-4C0E-8680-7AE283981624}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A302325B-D680-4C0E-8680-7AE283981624}.Release|Any CPU.Build.0 = Release|Any CPU - {A302325B-D680-4C0E-8680-7AE283981624}.Release|x64.ActiveCfg = Release|x64 - {A302325B-D680-4C0E-8680-7AE283981624}.Release|x64.Build.0 = Release|x64 - {A302325B-D680-4C0E-8680-7AE283981624}.Release|x86.ActiveCfg = Release|x86 - {A302325B-D680-4C0E-8680-7AE283981624}.Release|x86.Build.0 = Release|x86 - {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|Any CPU.ActiveCfg = FooBar|Any CPU - {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|Any CPU.Build.0 = FooBar|Any CPU - {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x64.ActiveCfg = FooBar|x64 - {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x64.Build.0 = FooBar|x64 - {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x86.ActiveCfg = FooBar|x86 - {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x86.Build.0 = FooBar|x86 - EndGlobalSection -EndGlobal -"; - - private const string ExpectedSlnFileAfterAddingProjectWithInRootOption = @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26006.2 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" -EndProject -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""Lib"", ""src\Lib\Lib.csproj"", ""{84A45D44-B677-492D-A6DA-B3A71135AB8E}"" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|x64.ActiveCfg = Debug|Any CPU - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|x64.Build.0 = Debug|Any CPU - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|x86.ActiveCfg = Debug|Any CPU - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|x86.Build.0 = Debug|Any CPU - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|Any CPU.Build.0 = Release|Any CPU - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|x64.ActiveCfg = Release|Any CPU - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|x64.Build.0 = Release|Any CPU - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|x86.ActiveCfg = Release|Any CPU - {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal -"; - - private const string ExpectedSlnFileAfterAddingProjectWithSolutionFolderOption = @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26006.2 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" -EndProject -Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""TestFolder"", ""TestFolder"", ""__SOLUTION_FOLDER_GUID__"" -EndProject -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""Lib"", ""src\Lib\Lib.csproj"", ""__LIB_PROJECT_GUID__"" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 - {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 - __LIB_PROJECT_GUID__.Debug|Any CPU.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|Any CPU.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Release|Any CPU.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|Any CPU.Build.0 = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - __LIB_PROJECT_GUID__ = __SOLUTION_FOLDER_GUID__ - EndGlobalSection -EndGlobal -"; - - [Theory] - [InlineData("sln", "--help")] - [InlineData("sln", "-h")] - [InlineData("sln", "-?")] - [InlineData("sln", "/?")] - [InlineData("solution", "--help")] - [InlineData("solution", "-h")] - [InlineData("solution", "-?")] - [InlineData("solution", "/?")] - public void WhenHelpOptionIsPassedItPrintsUsage(string solutionCommand, string helpArg) - { - var cmd = new DotnetCommand(Log) - .Execute(solutionCommand, "add", helpArg); - cmd.Should().Pass(); - cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(HelpText(Directory.GetCurrentDirectory())); - } - - [Theory] - [InlineData("sln", "")] - [InlineData("sln", "unknownCommandName")] - [InlineData("solution", "")] - [InlineData("solution", "unknownCommandName")] - public void WhenNoCommandIsPassedItPrintsError(string solutionCommand, string commandName) - { - var cmd = new DotnetCommand(Log) - .Execute($"{solutionCommand} {commandName}".Trim().Split()); - cmd.Should().Fail(); - cmd.StdErr.Should().Be(CommonLocalizableStrings.RequiredCommandNotPassed); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenTooManyArgumentsArePassedItPrintsError(string solutionCommand) - { - var cmd = new DotnetCommand(Log) - .Execute(solutionCommand, "one.sln", "two.sln", "three.sln", "add"); - cmd.Should().Fail(); - cmd.StdErr.Should().BeVisuallyEquivalentTo($@"{string.Format(CommandLineValidation.LocalizableStrings.UnrecognizedCommandOrArgument, "two.sln")} -{string.Format(CommandLineValidation.LocalizableStrings.UnrecognizedCommandOrArgument, "three.sln")}"); - } - - [Theory] - [InlineData("sln", "idontexist.sln")] - [InlineData("sln", "ihave?invalidcharacters")] - [InlineData("sln", "ihaveinv@lidcharacters")] - [InlineData("sln", "ihaveinvalid/characters")] - [InlineData("sln", "ihaveinvalidchar\\acters")] - [InlineData("solution", "idontexist.sln")] - [InlineData("solution", "ihave?invalidcharacters")] - [InlineData("solution", "ihaveinv@lidcharacters")] - [InlineData("solution", "ihaveinvalid/characters")] - [InlineData("solution", "ihaveinvalidchar\\acters")] - public void WhenNonExistingSolutionIsPassedItPrintsErrorAndUsage(string solutionCommand, string solutionName) - { - var cmd = new DotnetCommand(Log) - .Execute(solutionCommand, solutionName, "add", "p.csproj"); - cmd.Should().Fail(); - cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.CouldNotFindSolutionOrDirectory, solutionName)); - cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenInvalidSolutionIsPassedItPrintsErrorAndUsage(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("InvalidSolution", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var projectToAdd = Path.Combine("Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "InvalidSolution.sln", "add", projectToAdd); - cmd.Should().Fail(); - cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, "InvalidSolution.sln", LocalizableStrings.FileHeaderMissingError)); - cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenInvalidSolutionIsFoundAddPrintsErrorAndUsage(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("InvalidSolution", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var solutionPath = Path.Combine(projectDirectory, "InvalidSolution.sln"); - var projectToAdd = Path.Combine("Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "add", projectToAdd); - cmd.Should().Fail(); - cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, solutionPath, LocalizableStrings.FileHeaderMissingError)); - cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenNoProjectIsPassedItPrintsErrorAndUsage(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add"); - cmd.Should().Fail(); - cmd.StdErr.Should().Be(CommonLocalizableStrings.SpecifyAtLeastOneProjectToAdd); - cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenNoSolutionExistsInTheDirectoryAddPrintsErrorAndUsage(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var solutionPath = Path.Combine(projectDirectory, "App"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(solutionPath) - .Execute(solutionCommand, "add", "App.csproj"); - cmd.Should().Fail(); - cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.SolutionDoesNotExist, solutionPath + Path.DirectorySeparatorChar)); - cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenMoreThanOneSolutionExistsInTheDirectoryItPrintsErrorAndUsage(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithMultipleSlnFiles", identifier: "GivenDotnetSlnAdd") - .WithSource() - .Path; - - var projectToAdd = Path.Combine("Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "add", projectToAdd); - cmd.Should().Fail(); - cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.MoreThanOneSolutionInDirectory, projectDirectory + Path.DirectorySeparatorChar)); - cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenNestedProjectIsAddedSolutionFoldersAreCreated(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir") - .WithSource() - .Path; - - var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - - var slnPath = Path.Combine(projectDirectory, "App.sln"); - var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingNestedProj); - File.ReadAllText(slnPath) - .Should().BeVisuallyEquivalentTo(expectedSlnContents); - - cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute($"build", "App.sln"); - cmd.Should().Pass(); - } - - [Theory] - [InlineData("sln", true)] - [InlineData("sln", false)] - [InlineData("solution", true)] - [InlineData("solution", false)] - public void WhenNestedProjectIsAddedSolutionFoldersAreCreatedBuild(string solutionCommand, bool fooFirst) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojInSubDirVS", identifier: $"{solutionCommand}{fooFirst}") - .WithSource() - .Path; - string projectToAdd; - CommandResult cmd; - - if (fooFirst) - { - projectToAdd = "foo"; - cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - } - - projectToAdd = Path.Combine("foo", "bar"); - cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - - if (!fooFirst) - { - projectToAdd = "foo"; - cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - } - - cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute($"build", "App.sln"); - cmd.Should().Pass(); - - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenNestedDuplicateProjectIsAddedToASolutionFolder(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojInSubDirVSErrors", identifier: $"{solutionCommand}") - .WithSource() - .Path; - string projectToAdd; - CommandResult cmd; - - projectToAdd = Path.Combine("Base", "Second", "TestCollision.csproj"); - cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Fail() - .And.HaveStdErrContaining("TestCollision") - .And.HaveStdErrContaining("Base"); - - projectToAdd = Path.Combine("Base", "Second", "Third", "Second.csproj"); - cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Fail() - .And.HaveStdErrContaining("Second") - .And.HaveStdErrContaining("Base"); - } - - [Theory] - [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] - [InlineData("sln", "TestAppWithSlnAnd472CsprojFiles")] - [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] - [InlineData("solution", "TestAppWithSlnAnd472CsprojFiles")] - public void WhenDirectoryContainingProjectIsGivenProjectIsAdded(string solutionCommand, string testAsset) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") - .WithSource() - .Path; - - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "add", "Lib"); - cmd.Should().Pass(); - - var slnPath = Path.Combine(projectDirectory, "App.sln"); - var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingLibProj); - File.ReadAllText(slnPath) - .Should().BeVisuallyEquivalentTo(expectedSlnContents); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenDirectoryContainsNoProjectsItCancelsWholeOperation(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var slnFullPath = Path.Combine(projectDirectory, "App.sln"); - var contentBefore = File.ReadAllText(slnFullPath); - var directoryToAdd = "Empty"; - - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "add", directoryToAdd); - cmd.Should().Fail(); - cmd.StdErr.Should().Be( - string.Format( - CommonLocalizableStrings.CouldNotFindAnyProjectInDirectory, - Path.Combine(projectDirectory, directoryToAdd))); - - File.ReadAllText(slnFullPath) - .Should().BeVisuallyEquivalentTo(contentBefore); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenDirectoryContainsMultipleProjectsItCancelsWholeOperation(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var slnFullPath = Path.Combine(projectDirectory, "App.sln"); - var contentBefore = File.ReadAllText(slnFullPath); - var directoryToAdd = "Multiple"; - - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "add", directoryToAdd); - cmd.Should().Fail(); - cmd.StdErr.Should().Be( - string.Format( - CommonLocalizableStrings.MoreThanOneProjectInDirectory, - Path.Combine(projectDirectory, directoryToAdd))); - - File.ReadAllText(slnFullPath) - .Should().BeVisuallyEquivalentTo(contentBefore); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenProjectDirectoryIsAddedSolutionFoldersAreNotCreated(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var projectToAdd = Path.Combine("Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - - var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); - var solutionFolderProjects = slnFile.Projects.Where( - p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid); - solutionFolderProjects.Count().Should().Be(0); - slnFile.Sections.GetSection("NestedProjects").Should().BeNull(); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenSharedProjectAddedShouldStillBuild(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojFiles", $"{solutionCommand}") - .WithSource() - .Path; - - var projectToAdd = Path.Combine("Shared", "Shared.shproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - cmd.StdErr.Should().BeEmpty(); - - cmd = new DotnetBuildCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(); - cmd.Should().Pass(); - } - - [Theory] - [InlineData("sln", ".")] - [InlineData("sln", "")] - [InlineData("solution", ".")] - [InlineData("solution", "")] - public void WhenSolutionFolderExistsItDoesNotGetAdded(string solutionCommand, string firstComponent) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndSolutionFolders", identifier: $"{solutionCommand}{firstComponent}") - .WithSource() - .Path; - - var projectToAdd = Path.Combine($"{firstComponent}", "src", "src", "Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - - var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); - slnFile.Projects.Count().Should().Be(4); - - var solutionFolderProjects = slnFile.Projects.Where( - p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid); - solutionFolderProjects.Count().Should().Be(2); - - var solutionFolders = slnFile.Sections.GetSection("NestedProjects").Properties; - solutionFolders.Count.Should().Be(3); - - solutionFolders["{DDF3765C-59FB-4AA6-BE83-779ED13AA64A}"] - .Should().Be("{72BFCA87-B033-4721-8712-4D12166B4A39}"); - - var newlyAddedSrcFolder = solutionFolderProjects.Single(p => p.Id != "{72BFCA87-B033-4721-8712-4D12166B4A39}"); - solutionFolders[newlyAddedSrcFolder.Id] - .Should().Be("{72BFCA87-B033-4721-8712-4D12166B4A39}"); - - var libProject = slnFile.Projects.Single(p => p.Name == "Lib"); - solutionFolders[libProject.Id] - .Should().Be(newlyAddedSrcFolder.Id); - } - - [Theory] - [InlineData("sln", "TestAppWithSlnAndCsprojFiles", ExpectedSlnFileAfterAddingLibProj, "")] - [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles", ExpectedSlnFileAfterAddingLibProj, "{84A45D44-B677-492D-A6DA-B3A71135AB8E}")] - [InlineData("sln", "TestAppWithEmptySln", ExpectedSlnFileAfterAddingLibProjToEmptySln, "")] - [InlineData("solution", "TestAppWithSlnAndCsprojFiles", ExpectedSlnFileAfterAddingLibProj, "")] - [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles", ExpectedSlnFileAfterAddingLibProj, "{84A45D44-B677-492D-A6DA-B3A71135AB8E}")] - [InlineData("solution", "TestAppWithEmptySln", ExpectedSlnFileAfterAddingLibProjToEmptySln, "")] - public void WhenValidProjectIsPassedBuildConfigsAreAdded( - string solutionCommand, - string testAsset, - string expectedSlnContentsTemplate, - string expectedProjectGuid) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset(testAsset, $"{solutionCommand}{testAsset}") - .WithSource() - .Path; - - var projectToAdd = "Lib/Lib.csproj"; - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - - var slnPath = Path.Combine(projectDirectory, "App.sln"); - - var expectedSlnContents = GetExpectedSlnContents( - slnPath, - expectedSlnContentsTemplate, - expectedProjectGuid); - - File.ReadAllText(slnPath) - .Should().BeVisuallyEquivalentTo(expectedSlnContents); - } - - [Theory] - [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] - [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] - [InlineData("sln", "TestAppWithEmptySln")] - [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] - [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] - [InlineData("solution", "TestAppWithEmptySln")] - public void WhenValidProjectIsPassedItGetsAdded(string solutionCommand, string testAsset) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") - .WithSource() - .Path; - - var projectToAdd = "Lib/Lib.csproj"; - var projectPath = Path.Combine("Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - cmd.StdOut.Should().Be(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectPath)); - cmd.StdErr.Should().BeEmpty(); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenProjectIsAddedSolutionHasUTF8BOM(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithEmptySln", $"{solutionCommand}") - .WithSource() - .Path; - - var projectToAdd = "Lib/Lib.csproj"; - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - - var preamble = Encoding.UTF8.GetPreamble(); - preamble.Length.Should().Be(3); - using (var stream = new FileStream(Path.Combine(projectDirectory, "App.sln"), FileMode.Open)) - { - var bytes = new byte[preamble.Length]; -#pragma warning disable CA2022 // Avoid inexact read - stream.Read(bytes, 0, bytes.Length); -#pragma warning restore CA2022 // Avoid inexact read - bytes.Should().BeEquivalentTo(preamble); - } - } - - [Theory] - [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] - [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] - [InlineData("sln", "TestAppWithEmptySln")] - [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] - [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] - [InlineData("solution", "TestAppWithEmptySln")] - public void WhenInvalidProjectIsPassedItDoesNotGetAdded(string solutionCommand, string testAsset) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset(testAsset, $"{solutionCommand}{testAsset}") - .WithSource() - .Path; - - var projectToAdd = "Lib/Library.cs"; - var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); - var expectedNumberOfProjects = slnFile.Projects.Count(); - - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - cmd.StdOut.Should().BeEmpty(); - cmd.StdErr.Should().Match(string.Format(CommonLocalizableStrings.InvalidProjectWithExceptionMessage, '*', '*')); - - slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); - slnFile.Projects.Count().Should().Be(expectedNumberOfProjects); - } - - [Theory] - [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] - [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] - [InlineData("sln", "TestAppWithEmptySln")] - [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] - [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] - [InlineData("solution", "TestAppWithEmptySln")] - public void WhenValidProjectIsPassedTheSlnBuilds(string solutionCommand, string testAsset) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") - .WithSource() - .Path; - - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", "App/App.csproj", "Lib/Lib.csproj"); - cmd.Should().Pass(); - - var slnPath = Path.Combine(projectDirectory, "App.sln"); - - new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute($"restore", "App.sln") - .Should().Pass(); - - new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute("build", "App.sln", "--configuration", "Release") - .Should().Pass(); - - var reasonString = "should be built in release mode, otherwise it means build configurations are missing from the sln file"; - - var appPathCalculator = OutputPathCalculator.FromProject(Path.Combine(projectDirectory, "App", "App.csproj")); - new DirectoryInfo(appPathCalculator.GetOutputDirectory(configuration: "Debug")).Should().NotExist(reasonString); - new DirectoryInfo(appPathCalculator.GetOutputDirectory(configuration: "Release")).Should().Exist() - .And.HaveFile("App.dll"); - - var libPathCalculator = OutputPathCalculator.FromProject(Path.Combine(projectDirectory, "Lib", "Lib.csproj")); - new DirectoryInfo(libPathCalculator.GetOutputDirectory(configuration: "Debug")).Should().NotExist(reasonString); - new DirectoryInfo(libPathCalculator.GetOutputDirectory(configuration: "Release")).Should().Exist() - .And.HaveFile("Lib.dll"); - } - - [Theory] - [InlineData("sln", "TestAppWithSlnAndExistingCsprojReferences")] - [InlineData("sln", "TestAppWithSlnAndExistingCsprojReferencesWithEscapedDirSep")] - [InlineData("solution", "TestAppWithSlnAndExistingCsprojReferences")] - [InlineData("solution", "TestAppWithSlnAndExistingCsprojReferencesWithEscapedDirSep")] - public void WhenSolutionAlreadyContainsProjectItDoesntDuplicate(string solutionCommand, string testAsset) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") - .WithSource() - .Path; - - var solutionPath = Path.Combine(projectDirectory, "App.sln"); - var projectToAdd = Path.Combine("Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - cmd.StdOut.Should().Be(string.Format(CommonLocalizableStrings.SolutionAlreadyContainsProject, solutionPath, projectToAdd)); - cmd.StdErr.Should().BeEmpty(); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenPassedMultipleProjectsAndOneOfthemDoesNotExistItCancelsWholeOperation(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var slnFullPath = Path.Combine(projectDirectory, "App.sln"); - var contentBefore = File.ReadAllText(slnFullPath); - - var projectToAdd = Path.Combine("Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd, "idonotexist.csproj"); - cmd.Should().Fail(); - cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.CouldNotFindProjectOrDirectory, "idonotexist.csproj")); - - File.ReadAllText(slnFullPath) - .Should().BeVisuallyEquivalentTo(contentBefore); - } - - [Theory(Skip = "https://github.com/dotnet/sdk/issues/522")] - [InlineData("sln")] - [InlineData("solution")] - public void WhenPassedAnUnknownProjectTypeItFails(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("SlnFileWithNoProjectReferencesAndUnknownProject", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var slnFullPath = Path.Combine(projectDirectory, "App.sln"); - var contentBefore = File.ReadAllText(slnFullPath); - - var projectToAdd = Path.Combine("UnknownProject", "UnknownProject.unknownproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Fail(); - cmd.StdErr.Should().BeVisuallyEquivalentTo("has an unknown project type and cannot be added to the solution file. Contact your SDK provider for support."); - - File.ReadAllText(slnFullPath) - .Should().BeVisuallyEquivalentTo(contentBefore); - } - - [Theory] - [InlineData("sln", "SlnFileWithNoProjectReferencesAndCSharpProject", "CSharpProject", "CSharpProject.csproj", ProjectTypeGuids.CSharpProjectTypeGuid)] - [InlineData("sln", "SlnFileWithNoProjectReferencesAndFSharpProject", "FSharpProject", "FSharpProject.fsproj", ProjectTypeGuids.FSharpProjectTypeGuid)] - [InlineData("sln", "SlnFileWithNoProjectReferencesAndVBProject", "VBProject", "VBProject.vbproj", ProjectTypeGuids.VBProjectTypeGuid)] - [InlineData("sln", "SlnFileWithNoProjectReferencesAndUnknownProjectWithSingleProjectTypeGuid", "UnknownProject", "UnknownProject.unknownproj", "{130159A9-F047-44B3-88CF-0CF7F02ED50F}")] - [InlineData("sln", "SlnFileWithNoProjectReferencesAndUnknownProjectWithMultipleProjectTypeGuids", "UnknownProject", "UnknownProject.unknownproj", "{130159A9-F047-44B3-88CF-0CF7F02ED50F}")] - [InlineData("solution", "SlnFileWithNoProjectReferencesAndCSharpProject", "CSharpProject", "CSharpProject.csproj", ProjectTypeGuids.CSharpProjectTypeGuid)] - [InlineData("solution", "SlnFileWithNoProjectReferencesAndFSharpProject", "FSharpProject", "FSharpProject.fsproj", ProjectTypeGuids.FSharpProjectTypeGuid)] - [InlineData("solution", "SlnFileWithNoProjectReferencesAndVBProject", "VBProject", "VBProject.vbproj", ProjectTypeGuids.VBProjectTypeGuid)] - [InlineData("solution", "SlnFileWithNoProjectReferencesAndUnknownProjectWithSingleProjectTypeGuid", "UnknownProject", "UnknownProject.unknownproj", "{130159A9-F047-44B3-88CF-0CF7F02ED50F}")] - [InlineData("solution", "SlnFileWithNoProjectReferencesAndUnknownProjectWithMultipleProjectTypeGuids", "UnknownProject", "UnknownProject.unknownproj", "{130159A9-F047-44B3-88CF-0CF7F02ED50F}")] - public void WhenPassedAProjectItAddsCorrectProjectTypeGuid( - string solutionCommand, - string testAsset, - string projectDir, - string projectName, - string expectedTypeGuid) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") - .WithSource() - .Path; - - var projectToAdd = Path.Combine(projectDir, projectName); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - cmd.StdErr.Should().BeEmpty(); - cmd.StdOut.Should().Be(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectToAdd)); - - var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); - var nonSolutionFolderProjects = slnFile.Projects.Where( - p => p.TypeGuid != ProjectTypeGuids.SolutionFolderGuid); - nonSolutionFolderProjects.Count().Should().Be(1); - nonSolutionFolderProjects.Single().TypeGuid.Should().Be(expectedTypeGuid); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenPassedAProjectWithoutATypeGuidItErrors(string solutionCommand) - { - var solutionDirectory = _testAssetsManager - .CopyTestAsset("SlnFileWithNoProjectReferencesAndUnknownProjectType", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var solutionPath = Path.Combine(solutionDirectory, "App.sln"); - var contentBefore = File.ReadAllText(solutionPath); - - var projectToAdd = Path.Combine("UnknownProject", "UnknownProject.unknownproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(solutionDirectory) - .Execute(solutionCommand, "add", projectToAdd); - cmd.Should().Pass(); - cmd.StdErr.Should().Be( - string.Format( - CommonLocalizableStrings.UnsupportedProjectType, - Path.Combine(solutionDirectory, projectToAdd))); - cmd.StdOut.Should().BeEmpty(); - - File.ReadAllText(solutionPath) - .Should() - .BeVisuallyEquivalentTo(contentBefore); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - private void WhenSlnContainsSolutionFolderWithDifferentCasingItDoesNotCreateDuplicate(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCaseSensitiveSolutionFolders", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", projectToAdd); - cmd.Should().Pass(); - - var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); - var solutionFolderProjects = slnFile.Projects.Where( - p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid); - solutionFolderProjects.Count().Should().Be(1); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenProjectWithoutMatchingConfigurationsIsAddedSolutionMapsToFirstAvailable(string solutionCommand) - { - var slnDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndProjectConfigs", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var slnFullPath = Path.Combine(slnDirectory, "App.sln"); - - var result = new DotnetCommand(Log) - .WithWorkingDirectory(slnDirectory) - .Execute(solutionCommand, "add", "ProjectWithoutMatchingConfigs"); - result.Should().Pass(); - - File.ReadAllText(slnFullPath) - .Should().BeVisuallyEquivalentTo(ExpectedSlnFileAfterAddingProjectWithoutMatchingConfigs); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenProjectWithMatchingConfigurationsIsAddedSolutionMapsAll(string solutionCommand) - { - var slnDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndProjectConfigs", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var slnFullPath = Path.Combine(slnDirectory, "App.sln"); - - var result = new DotnetCommand(Log) - .WithWorkingDirectory(slnDirectory) - .Execute(solutionCommand, "add", "ProjectWithMatchingConfigs"); - result.Should().Pass(); - - File.ReadAllText(slnFullPath) - .Should().BeVisuallyEquivalentTo(ExpectedSlnFileAfterAddingProjectWithMatchingConfigs); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenProjectWithAdditionalConfigurationsIsAddedSolutionDoesNotMapThem(string solutionCommand) - { - var slnDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndProjectConfigs", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var slnFullPath = Path.Combine(slnDirectory, "App.sln"); - - var result = new DotnetCommand(Log) - .WithWorkingDirectory(slnDirectory) - .Execute(solutionCommand, "add", "ProjectWithAdditionalConfigs"); - result.Should().Pass(); - - File.ReadAllText(slnFullPath) - .Should().BeVisuallyEquivalentTo(ExpectedSlnFileAfterAddingProjectWithAdditionalConfigs); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void ItAddsACSharpProjectThatIsMultitargeted(string solutionCommand) - { - var solutionDirectory = _testAssetsManager - .CopyTestAsset("TestAppsWithSlnAndMultitargetedProjects", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var projectToAdd = Path.Combine("MultitargetedCS", "MultitargetedCS.csproj"); - - new DotnetCommand(Log) - .WithWorkingDirectory(solutionDirectory) - .Execute(solutionCommand, "add", projectToAdd) - .Should() - .Pass() - .And - .HaveStdOutContaining(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectToAdd)); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void ItAddsAVisualBasicProjectThatIsMultitargeted(string solutionCommand) - { - var solutionDirectory = _testAssetsManager - .CopyTestAsset("TestAppsWithSlnAndMultitargetedProjects", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var projectToAdd = Path.Combine("MultitargetedVB", "MultitargetedVB.vbproj"); - - new DotnetCommand(Log) - .WithWorkingDirectory(solutionDirectory) - .Execute(solutionCommand, "add", projectToAdd) - .Should() - .Pass() - .And - .HaveStdOutContaining(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectToAdd)); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void ItAddsAnFSharpProjectThatIsMultitargeted(string solutionCommand) - { - var solutionDirectory = _testAssetsManager - .CopyTestAsset("TestAppsWithSlnAndMultitargetedProjects", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var slnFullPath = Path.Combine(solutionDirectory, "App.sln"); - var projectToAdd = Path.Combine("MultitargetedFS", "MultitargetedFS.fsproj"); - - new DotnetCommand(Log) - .WithWorkingDirectory(solutionDirectory) - .Execute(solutionCommand, "add", projectToAdd) - .Should() - .Pass() - .And - .HaveStdOutContaining(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectToAdd)); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenNestedProjectIsAddedAndInRootOptionIsPassedNoSolutionFoldersAreCreated(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", "--in-root", projectToAdd); - cmd.Should().Pass(); - - var slnPath = Path.Combine(projectDirectory, "App.sln"); - var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingProjectWithInRootOption); - File.ReadAllText(slnPath) - .Should().BeVisuallyEquivalentTo(expectedSlnContents); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenSolutionFolderIsPassedProjectsAreAddedThere(string solutionCommand) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", "--solution-folder", "TestFolder", projectToAdd); - cmd.Should().Pass(); - - var slnPath = Path.Combine(projectDirectory, "App.sln"); - var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingProjectWithSolutionFolderOption); - File.ReadAllText(slnPath) - .Should().BeVisuallyEquivalentTo(expectedSlnContents); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenSolutionFolderAndInRootIsPassedItFails(string solutionCommand) - { - var solutionDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: $"{solutionCommand}") - .WithSource() - .Path; - - var solutionPath = Path.Combine(solutionDirectory, "App.sln"); - var contentBefore = File.ReadAllText(solutionPath); - - var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(solutionDirectory) - .Execute(solutionCommand, "App.sln", "add", "--solution-folder", "blah", "--in-root", projectToAdd); - cmd.Should().Fail(); - cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); - cmd.StdErr.Should().Be(Tools.Sln.LocalizableStrings.SolutionFolderAndInRootMutuallyExclusive); - cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); - - File.ReadAllText(solutionPath) - .Should() - .BeVisuallyEquivalentTo(contentBefore); - } - - [Theory] - [InlineData("sln", "/TestFolder//", "ForwardSlash")] - [InlineData("sln", "\\TestFolder\\\\", "BackwardSlash")] - [InlineData("solution", "/TestFolder//", "ForwardSlash")] - [InlineData("solution", "\\TestFolder\\\\", "BackwardSlash")] - public void WhenSolutionFolderIsPassedWithDirectorySeparatorFolderStructureIsCorrect(string solutionCommand, string solutionFolder, string testIdentifier) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: $"{solutionCommand}{testIdentifier}") - .WithSource() - .Path; - - var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "App.sln", "add", "--solution-folder", solutionFolder, projectToAdd); - cmd.Should().Pass(); - - var slnPath = Path.Combine(projectDirectory, "App.sln"); - var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingProjectWithSolutionFolderOption); - File.ReadAllText(slnPath) - .Should().BeVisuallyEquivalentTo(expectedSlnContents); - } - - private string GetExpectedSlnContents( - string slnPath, - string slnTemplate, - string expectedLibProjectGuid = null) - { - var slnFile = SlnFile.Read(slnPath); - - if (string.IsNullOrEmpty(expectedLibProjectGuid)) - { - var matchingProjects = slnFile.Projects - .Where((p) => p.FilePath.EndsWith("Lib.csproj")) - .ToList(); - - matchingProjects.Count.Should().Be(1); - var slnProject = matchingProjects[0]; - expectedLibProjectGuid = slnProject.Id; - } - var slnContents = slnTemplate.Replace("__LIB_PROJECT_GUID__", expectedLibProjectGuid); - - var matchingSrcFolder = slnFile.Projects - .Where((p) => p.FilePath == "src") - .ToList(); - if (matchingSrcFolder.Count == 1) - { - slnContents = slnContents.Replace("__SRC_FOLDER_GUID__", matchingSrcFolder[0].Id); - } - - var matchingSolutionFolder = slnFile.Projects - .Where((p) => p.FilePath == "TestFolder") - .ToList(); - if (matchingSolutionFolder.Count == 1) - { - slnContents = slnContents.Replace("__SOLUTION_FOLDER_GUID__", matchingSolutionFolder[0].Id); - } - - return slnContents; - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenSolutionIsPassedAsProjectItPrintsSuggestionAndUsage(string solutionCommand) - { - VerifySuggestionAndUsage(solutionCommand, ""); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenSolutionIsPassedAsProjectWithInRootItPrintsSuggestionAndUsage(string solutionCommand) - { - VerifySuggestionAndUsage(solutionCommand, "--in-root"); - } - - [Theory] - [InlineData("sln")] - [InlineData("solution")] - public void WhenSolutionIsPassedAsProjectWithSolutionFolderItPrintsSuggestionAndUsage(string solutionCommand) - { - VerifySuggestionAndUsage(solutionCommand, "--solution-folder"); - } - private void VerifySuggestionAndUsage(string solutionCommand, string arguments) - { - var projectDirectory = _testAssetsManager - .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}{arguments}") - .WithSource() - .Path; - - var projectArg = Path.Combine("Lib", "Lib.csproj"); - var cmd = new DotnetCommand(Log) - .WithWorkingDirectory(projectDirectory) - .Execute(solutionCommand, "add", arguments, "Lib", "App.sln", projectArg); - cmd.Should().Fail(); - cmd.StdErr.Should().BeVisuallyEquivalentTo( - string.Format(CommonLocalizableStrings.SolutionArgumentMisplaced, "App.sln") + Environment.NewLine - + CommonLocalizableStrings.DidYouMean + Environment.NewLine - + $" dotnet solution App.sln add {arguments} Lib {projectArg}" - ); - cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); - } - } -} diff --git a/test/dotnet-sln.Tests/add/GivenDotnetSlnAdd.cs b/test/dotnet-sln.Tests/add/GivenDotnetSlnAdd.cs new file mode 100644 index 000000000000..6466f09cbc74 --- /dev/null +++ b/test/dotnet-sln.Tests/add/GivenDotnetSlnAdd.cs @@ -0,0 +1,1458 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.Cli.Sln.Internal; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools; +using Microsoft.DotNet.Tools.Common; + +namespace Microsoft.DotNet.Cli.Sln.Add.Tests; + +public class GivenDotnetSlnAdd(ITestOutputHelper log) : SdkTest(log) +{ + private readonly Func HelpText = (defaultVal) => $@"Description: + Add one or more projects to a solution file. + +Usage: + dotnet solution [] add [...] [command] [options] + +Arguments: + The solution file to operate on. If not specified, the command will search the current directory for one. [default: {PathUtility.EnsureTrailingSlash(defaultVal)}] + The paths to the projects to add to the solution. + +Options: + --in-root Place project in root of the solution, rather than creating a solution folder. + -s, --solution-folder The destination solution folder path to add the projects to. + -?, -h, --help Show command line help. + +Commands: + file Add one or more solution items to a solution file. + folder Add one or more solution folders to a solution file."; + private const string ExpectedSlnFileAfterAddingLibProj = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App\App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""Lib"", ""Lib\Lib.csproj"", ""__LIB_PROJECT_GUID__"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + __LIB_PROJECT_GUID__.Debug|Any CPU.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|Any CPU.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Release|Any CPU.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|Any CPU.Build.0 = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingLibProjToEmptySln = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""Lib"", ""Lib\Lib.csproj"", ""__LIB_PROJECT_GUID__"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + __LIB_PROJECT_GUID__.Debug|Any CPU.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|Any CPU.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Release|Any CPU.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|Any CPU.Build.0 = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingNestedProj = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""src"", ""src"", ""__SRC_FOLDER_GUID__"" +EndProject +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""Lib"", ""src\Lib\Lib.csproj"", ""__LIB_PROJECT_GUID__"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + __LIB_PROJECT_GUID__.Debug|Any CPU.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|Any CPU.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Release|Any CPU.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|Any CPU.Build.0 = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + __LIB_PROJECT_GUID__ = __SRC_FOLDER_GUID__ + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingProjectWithoutMatchingConfigs = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ProjectWithoutMatchingConfigs"", ""ProjectWithoutMatchingConfigs\ProjectWithoutMatchingConfigs.csproj"", ""{C49B64DE-4401-4825-8A88-10DCB5950E57}"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + Foo Bar|Any CPU = Foo Bar|Any CPU + Foo Bar|x64 = Foo Bar|x64 + Foo Bar|x86 = Foo Bar|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x64.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x64.Build.0 = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x86.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x86.Build.0 = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|Any CPU.Build.0 = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x64.ActiveCfg = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x64.Build.0 = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x86.ActiveCfg = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x86.Build.0 = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|Any CPU.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|Any CPU.Build.0 = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x64.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x64.Build.0 = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x86.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x86.Build.0 = Debug|Any CPU + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingProjectWithMatchingConfigs = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ProjectWithMatchingConfigs"", ""ProjectWithMatchingConfigs\ProjectWithMatchingConfigs.csproj"", ""{C9601CA2-DB64-4FB6-B463-368C7764BF0D}"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + Foo Bar|Any CPU = Foo Bar|Any CPU + Foo Bar|x64 = Foo Bar|x64 + Foo Bar|x86 = Foo Bar|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x64.ActiveCfg = Debug|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x64.Build.0 = Debug|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x86.ActiveCfg = Debug|x86 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x86.Build.0 = Debug|x86 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|Any CPU.Build.0 = Release|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x64.ActiveCfg = Release|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x64.Build.0 = Release|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x86.ActiveCfg = Release|x86 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x86.Build.0 = Release|x86 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|Any CPU.ActiveCfg = FooBar|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|Any CPU.Build.0 = FooBar|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x64.ActiveCfg = FooBar|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x64.Build.0 = FooBar|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x86.ActiveCfg = FooBar|x86 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x86.Build.0 = FooBar|x86 + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingProjectWithAdditionalConfigs = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ProjectWithAdditionalConfigs"", ""ProjectWithAdditionalConfigs\ProjectWithAdditionalConfigs.csproj"", ""{A302325B-D680-4C0E-8680-7AE283981624}"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + Foo Bar|Any CPU = Foo Bar|Any CPU + Foo Bar|x64 = Foo Bar|x64 + Foo Bar|x86 = Foo Bar|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x64.ActiveCfg = Debug|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x64.Build.0 = Debug|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x86.ActiveCfg = Debug|x86 + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x86.Build.0 = Debug|x86 + {A302325B-D680-4C0E-8680-7AE283981624}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Release|Any CPU.Build.0 = Release|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Release|x64.ActiveCfg = Release|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Release|x64.Build.0 = Release|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Release|x86.ActiveCfg = Release|x86 + {A302325B-D680-4C0E-8680-7AE283981624}.Release|x86.Build.0 = Release|x86 + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|Any CPU.ActiveCfg = FooBar|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|Any CPU.Build.0 = FooBar|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x64.ActiveCfg = FooBar|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x64.Build.0 = FooBar|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x86.ActiveCfg = FooBar|x86 + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x86.Build.0 = FooBar|x86 + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingProjectWithInRootOption = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""Lib"", ""src\Lib\Lib.csproj"", ""{84A45D44-B677-492D-A6DA-B3A71135AB8E}"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|x64.ActiveCfg = Debug|Any CPU + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|x64.Build.0 = Debug|Any CPU + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|x86.ActiveCfg = Debug|Any CPU + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Debug|x86.Build.0 = Debug|Any CPU + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|Any CPU.Build.0 = Release|Any CPU + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|x64.ActiveCfg = Release|Any CPU + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|x64.Build.0 = Release|Any CPU + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|x86.ActiveCfg = Release|Any CPU + {84A45D44-B677-492D-A6DA-B3A71135AB8E}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingProjectWithSolutionFolderOption = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""TestFolder"", ""TestFolder"", ""__SOLUTION_FOLDER_GUID__"" +EndProject +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""Lib"", ""src\Lib\Lib.csproj"", ""__LIB_PROJECT_GUID__"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + __LIB_PROJECT_GUID__.Debug|Any CPU.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|Any CPU.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Release|Any CPU.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|Any CPU.Build.0 = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + __LIB_PROJECT_GUID__ = __SOLUTION_FOLDER_GUID__ + EndGlobalSection +EndGlobal +"; + + [Theory] + [InlineData("sln", "--help")] + [InlineData("sln", "-h")] + [InlineData("sln", "-?")] + [InlineData("sln", "/?")] + [InlineData("solution", "--help")] + [InlineData("solution", "-h")] + [InlineData("solution", "-?")] + [InlineData("solution", "/?")] + public void WhenHelpOptionIsPassedItPrintsUsage(string solutionCommand, string helpArg) + { + var cmd = new DotnetCommand(Log) + .Execute(solutionCommand, "add", helpArg); + cmd.Should().Pass(); + cmd.StdErr.Should().BeEmpty(); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(HelpText(Directory.GetCurrentDirectory())); + } + + [Theory] + [InlineData("sln", "")] + [InlineData("sln", "unknownCommandName")] + [InlineData("solution", "")] + [InlineData("solution", "unknownCommandName")] + public void WhenNoCommandIsPassedItPrintsError(string solutionCommand, string commandName) + { + var cmd = new DotnetCommand(Log) + .Execute($"{solutionCommand} {commandName}".Trim().Split()); + cmd.Should().Fail(); + cmd.StdErr.Should().Be(CommonLocalizableStrings.RequiredCommandNotPassed); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenTooManyArgumentsArePassedItPrintsError(string solutionCommand) + { + var cmd = new DotnetCommand(Log) + .Execute(solutionCommand, "one.sln", "two.sln", "three.sln", "add", "folder", "myFolder"); + cmd.Should().Fail(); + cmd.StdErr.Should().BeVisuallyEquivalentTo($@"{string.Format(CommandLineValidation.LocalizableStrings.UnrecognizedCommandOrArgument, "two.sln")} +{string.Format(CommandLineValidation.LocalizableStrings.UnrecognizedCommandOrArgument, "three.sln")}"); + } + + [Theory] + [InlineData("sln", "idontexist.sln")] + [InlineData("sln", "ihave?invalidcharacters")] + [InlineData("sln", "ihaveinv@lidcharacters")] + [InlineData("sln", "ihaveinvalid/characters")] + [InlineData("sln", "ihaveinvalidchar\\acters")] + [InlineData("solution", "idontexist.sln")] + [InlineData("solution", "ihave?invalidcharacters")] + [InlineData("solution", "ihaveinv@lidcharacters")] + [InlineData("solution", "ihaveinvalid/characters")] + [InlineData("solution", "ihaveinvalidchar\\acters")] + public void WhenNonExistingSolutionIsPassedItPrintsErrorAndUsage(string solutionCommand, string solutionName) + { + var cmd = new DotnetCommand(Log) + .Execute(solutionCommand, solutionName, "add", "p.csproj"); + cmd.Should().Fail(); + cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.CouldNotFindSolutionOrDirectory, solutionName)); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenInvalidSolutionIsPassedItPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("InvalidSolution", identifier: solutionCommand) + .WithSource() + .Path; + + var projectToAdd = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "InvalidSolution.sln", "add", projectToAdd); + cmd.Should().Fail(); + cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, "InvalidSolution.sln", LocalizableStrings.FileHeaderMissingError)); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenInvalidSolutionIsFoundAddPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("InvalidSolution", identifier: solutionCommand) + .WithSource() + .Path; + + var solutionPath = Path.Combine(projectDirectory, "InvalidSolution.sln"); + var projectToAdd = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "add", projectToAdd); + cmd.Should().Fail(); + cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, solutionPath, LocalizableStrings.FileHeaderMissingError)); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNoProjectIsPassedItPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add"); + cmd.Should().Fail(); + cmd.StdErr.Should().Be(CommonLocalizableStrings.SpecifyAtLeastOneProjectToAdd); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNoSolutionExistsInTheDirectoryAddPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var solutionPath = Path.Combine(projectDirectory, "App"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(solutionPath) + .Execute(solutionCommand, "add", "App.csproj"); + cmd.Should().Fail(); + cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.SolutionDoesNotExist, solutionPath + Path.DirectorySeparatorChar)); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenMoreThanOneSolutionExistsInTheDirectoryItPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithMultipleSlnFiles", identifier: "GivenDotnetSlnAdd") + .WithSource() + .Path; + + var projectToAdd = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "add", projectToAdd); + cmd.Should().Fail(); + cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.MoreThanOneSolutionInDirectory, projectDirectory + Path.DirectorySeparatorChar)); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNestedProjectIsAddedSolutionFoldersAreCreated(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass() + .And.HaveStdOut(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectToAdd)) + .And.HaveStdErr(""); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingNestedProj); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + + cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("build", "App.sln"); + cmd.Should().Pass(); + } + + [Theory] + [InlineData("sln", true)] + [InlineData("sln", false)] + [InlineData("solution", true)] + [InlineData("solution", false)] + public void WhenNestedProjectIsAddedSolutionFoldersAreCreatedBuild(string solutionCommand, bool fooFirst) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDirVS", identifier: $"{solutionCommand}{fooFirst}") + .WithSource() + .Path; + string projectToAdd; + CommandResult cmd; + + if (fooFirst) + { + projectToAdd = "foo"; + cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + } + + projectToAdd = Path.Combine("foo", "bar"); + cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + + if (!fooFirst) + { + projectToAdd = "foo"; + cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + } + + cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("build", "App.sln"); + cmd.Should().Pass(); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNestedDuplicateProjectIsAddedToASolutionFolder(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDirVSErrors", identifier: solutionCommand) + .WithSource() + .Path; + string projectToAdd; + CommandResult cmd; + + projectToAdd = Path.Combine("Base", "Second", "TestCollision.csproj"); + cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Fail() + .And.HaveStdErrContaining("TestCollision") + .And.HaveStdErrContaining("Base"); + + projectToAdd = Path.Combine("Base", "Second", "Third", "Second.csproj"); + cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Fail() + .And.HaveStdErrContaining("Second") + .And.HaveStdErrContaining("Base"); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] + [InlineData("sln", "TestAppWithSlnAnd472CsprojFiles")] + [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] + [InlineData("solution", "TestAppWithSlnAnd472CsprojFiles")] + public void WhenDirectoryContainingProjectIsGivenProjectIsAdded(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + var relativeProjectPath = Path.Combine("Lib", "Lib.csproj"); + + var directoryPath = Path.GetDirectoryName(relativeProjectPath); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "add", directoryPath); + cmd.Should().Pass() + .And.HaveStdOut(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, relativeProjectPath)) + .And.HaveStdErr(""); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingLibProj); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenDirectoryContainsNoProjectsItCancelsWholeOperation(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var slnFullPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnFullPath); + var directoryToAdd = "Empty"; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "add", directoryToAdd); + cmd.Should().Fail(); + cmd.StdErr.Should().Be( + string.Format( + CommonLocalizableStrings.CouldNotFindAnyProjectInDirectory, + Path.Combine(projectDirectory, directoryToAdd))); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenDirectoryContainsMultipleProjectsItCancelsWholeOperation(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var slnFullPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnFullPath); + var directoryToAdd = "Multiple"; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "add", directoryToAdd); + cmd.Should().Fail(); + cmd.StdErr.Should().Be( + string.Format( + CommonLocalizableStrings.MoreThanOneProjectInDirectory, + Path.Combine(projectDirectory, directoryToAdd))); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenProjectDirectoryIsAddedSolutionFoldersAreNotCreated(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var projectToAdd = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + + var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); + var solutionFolderProjects = slnFile.Projects.Where( + p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid); + solutionFolderProjects.Count().Should().Be(0); + slnFile.Sections.GetSection("NestedProjects").Should().BeNull(); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSharedProjectAddedShouldStillBuild(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var projectToAdd = Path.Combine("Shared", "Shared.shproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + cmd.StdErr.Should().BeEmpty(); + + cmd = new DotnetBuildCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(); + cmd.Should().Pass(); + } + + [Theory] + [InlineData("sln", ".")] + [InlineData("sln", "")] + [InlineData("solution", ".")] + [InlineData("solution", "")] + public void WhenSolutionFolderExistsItDoesNotGetAdded(string solutionCommand, string firstComponent) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndSolutionFolders", identifier: $"{solutionCommand}{firstComponent}") + .WithSource() + .Path; + + var projectToAdd = Path.Combine(firstComponent, "src", "src", "Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + + var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); + slnFile.Projects.Count.Should().Be(4); + + var solutionFolderProjects = slnFile.Projects.Where( + p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid); + solutionFolderProjects.Count().Should().Be(2); + + var solutionFolders = slnFile.Sections.GetSection("NestedProjects").Properties; + solutionFolders.Count.Should().Be(3); + + solutionFolders["{DDF3765C-59FB-4AA6-BE83-779ED13AA64A}"] + .Should().Be("{72BFCA87-B033-4721-8712-4D12166B4A39}"); + + var newlyAddedSrcFolder = solutionFolderProjects.Single(p => p.Id != "{72BFCA87-B033-4721-8712-4D12166B4A39}"); + solutionFolders[newlyAddedSrcFolder.Id] + .Should().Be("{72BFCA87-B033-4721-8712-4D12166B4A39}"); + + var libProject = slnFile.Projects.Single(p => p.Name == "Lib"); + solutionFolders[libProject.Id] + .Should().Be(newlyAddedSrcFolder.Id); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndCsprojFiles", ExpectedSlnFileAfterAddingLibProj, "")] + [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles", ExpectedSlnFileAfterAddingLibProj, "{84A45D44-B677-492D-A6DA-B3A71135AB8E}")] + [InlineData("sln", "TestAppWithEmptySln", ExpectedSlnFileAfterAddingLibProjToEmptySln, "")] + [InlineData("solution", "TestAppWithSlnAndCsprojFiles", ExpectedSlnFileAfterAddingLibProj, "")] + [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles", ExpectedSlnFileAfterAddingLibProj, "{84A45D44-B677-492D-A6DA-B3A71135AB8E}")] + [InlineData("solution", "TestAppWithEmptySln", ExpectedSlnFileAfterAddingLibProjToEmptySln, "")] + public void WhenValidProjectIsPassedBuildConfigsAreAdded( + string solutionCommand, + string testAsset, + string expectedSlnContentsTemplate, + string expectedProjectGuid) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + var projectToAdd = "Lib/Lib.csproj"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + + var expectedSlnContents = GetExpectedSlnContents( + slnPath, + expectedSlnContentsTemplate, + expectedProjectGuid); + + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] + [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("sln", "TestAppWithEmptySln")] + [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] + [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("solution", "TestAppWithEmptySln")] + public void WhenValidProjectIsPassedItGetsAdded(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + var projectToAdd = "Lib/Lib.csproj"; + var projectPath = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + cmd.StdOut.Should().Be(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectPath)); + cmd.StdErr.Should().BeEmpty(); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenProjectIsAddedSolutionHasUTF8BOM(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithEmptySln", identifier: solutionCommand) + .WithSource() + .Path; + + var projectToAdd = "Lib/Lib.csproj"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + + var preamble = Encoding.UTF8.GetPreamble(); + preamble.Length.Should().Be(3); + using (var stream = new FileStream(Path.Combine(projectDirectory, "App.sln"), FileMode.Open)) + { + var bytes = new byte[preamble.Length]; +#pragma warning disable CA2022 // Avoid inexact read + stream.Read(bytes, 0, bytes.Length); +#pragma warning restore CA2022 // Avoid inexact read + bytes.Should().BeEquivalentTo(preamble); + } + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] + [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("sln", "TestAppWithEmptySln")] + [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] + [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("solution", "TestAppWithEmptySln")] + public void WhenInvalidProjectIsPassedItDoesNotGetAdded(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + var projectToAdd = "Lib/Library.cs"; + var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); + var expectedNumberOfProjects = slnFile.Projects.Count; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + cmd.StdOut.Should().BeEmpty(); + cmd.StdErr.Should().Match(string.Format(CommonLocalizableStrings.InvalidProjectWithExceptionMessage, '*', '*')); + + slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); + slnFile.Projects.Count.Should().Be(expectedNumberOfProjects); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] + [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("sln", "TestAppWithEmptySln")] + [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] + [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("solution", "TestAppWithEmptySln")] + public void WhenValidProjectIsPassedTheSlnBuilds(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "App/App.csproj", "Lib/Lib.csproj"); + cmd.Should().Pass(); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("restore", "App.sln") + .Should().Pass(); + + new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute("build", "App.sln", "--configuration", "Release") + .Should().Pass(); + + var reasonString = "should be built in release mode, otherwise it means build configurations are missing from the sln file"; + + var appPathCalculator = OutputPathCalculator.FromProject(Path.Combine(projectDirectory, "App", "App.csproj")); + new DirectoryInfo(appPathCalculator.GetOutputDirectory(configuration: "Debug")).Should().NotExist(reasonString); + new DirectoryInfo(appPathCalculator.GetOutputDirectory(configuration: "Release")).Should().Exist() + .And.HaveFile("App.dll"); + + var libPathCalculator = OutputPathCalculator.FromProject(Path.Combine(projectDirectory, "Lib", "Lib.csproj")); + new DirectoryInfo(libPathCalculator.GetOutputDirectory(configuration: "Debug")).Should().NotExist(reasonString); + new DirectoryInfo(libPathCalculator.GetOutputDirectory(configuration: "Release")).Should().Exist() + .And.HaveFile("Lib.dll"); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndExistingCsprojReferences")] + [InlineData("sln", "TestAppWithSlnAndExistingCsprojReferencesWithEscapedDirSep")] + [InlineData("solution", "TestAppWithSlnAndExistingCsprojReferences")] + [InlineData("solution", "TestAppWithSlnAndExistingCsprojReferencesWithEscapedDirSep")] + public void WhenSolutionAlreadyContainsProjectItDoesntDuplicate(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + var solutionPath = Path.Combine(projectDirectory, "App.sln"); + var projectToAdd = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + cmd.StdOut.Should().Be(string.Format(CommonLocalizableStrings.SolutionAlreadyContainsProject, solutionPath, projectToAdd)); + cmd.StdErr.Should().BeEmpty(); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenPassedMultipleProjectsAndOneOfthemDoesNotExistItCancelsWholeOperation(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var slnFullPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnFullPath); + + var projectToAdd = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd, "idonotexist.csproj"); + cmd.Should().Fail(); + cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.CouldNotFindProjectOrDirectory, "idonotexist.csproj")); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(contentBefore); + } + + [Theory(Skip = "https://github.com/dotnet/sdk/issues/522")] + [InlineData("sln")] + [InlineData("solution")] + public void WhenPassedAnUnknownProjectTypeItFails(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("SlnFileWithNoProjectReferencesAndUnknownProject", identifier: solutionCommand) + .WithSource() + .Path; + + var slnFullPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnFullPath); + + var projectToAdd = Path.Combine("UnknownProject", "UnknownProject.unknownproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Fail(); + cmd.StdErr.Should().BeVisuallyEquivalentTo("has an unknown project type and cannot be added to the solution file. Contact your SDK provider for support."); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln", "SlnFileWithNoProjectReferencesAndCSharpProject", "CSharpProject", "CSharpProject.csproj", ProjectTypeGuids.CSharpProjectTypeGuid)] + [InlineData("sln", "SlnFileWithNoProjectReferencesAndFSharpProject", "FSharpProject", "FSharpProject.fsproj", ProjectTypeGuids.FSharpProjectTypeGuid)] + [InlineData("sln", "SlnFileWithNoProjectReferencesAndVBProject", "VBProject", "VBProject.vbproj", ProjectTypeGuids.VBProjectTypeGuid)] + [InlineData("sln", "SlnFileWithNoProjectReferencesAndUnknownProjectWithSingleProjectTypeGuid", "UnknownProject", "UnknownProject.unknownproj", "{130159A9-F047-44B3-88CF-0CF7F02ED50F}")] + [InlineData("sln", "SlnFileWithNoProjectReferencesAndUnknownProjectWithMultipleProjectTypeGuids", "UnknownProject", "UnknownProject.unknownproj", "{130159A9-F047-44B3-88CF-0CF7F02ED50F}")] + [InlineData("solution", "SlnFileWithNoProjectReferencesAndCSharpProject", "CSharpProject", "CSharpProject.csproj", ProjectTypeGuids.CSharpProjectTypeGuid)] + [InlineData("solution", "SlnFileWithNoProjectReferencesAndFSharpProject", "FSharpProject", "FSharpProject.fsproj", ProjectTypeGuids.FSharpProjectTypeGuid)] + [InlineData("solution", "SlnFileWithNoProjectReferencesAndVBProject", "VBProject", "VBProject.vbproj", ProjectTypeGuids.VBProjectTypeGuid)] + [InlineData("solution", "SlnFileWithNoProjectReferencesAndUnknownProjectWithSingleProjectTypeGuid", "UnknownProject", "UnknownProject.unknownproj", "{130159A9-F047-44B3-88CF-0CF7F02ED50F}")] + [InlineData("solution", "SlnFileWithNoProjectReferencesAndUnknownProjectWithMultipleProjectTypeGuids", "UnknownProject", "UnknownProject.unknownproj", "{130159A9-F047-44B3-88CF-0CF7F02ED50F}")] + public void WhenPassedAProjectItAddsCorrectProjectTypeGuid( + string solutionCommand, + string testAsset, + string projectDir, + string projectName, + string expectedTypeGuid) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + var projectToAdd = Path.Combine(projectDir, projectName); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + cmd.StdErr.Should().BeEmpty(); + cmd.StdOut.Should().Be(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectToAdd)); + + var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); + var nonSolutionFolderProjects = slnFile.Projects.Where( + p => p.TypeGuid != ProjectTypeGuids.SolutionFolderGuid); + nonSolutionFolderProjects.Count().Should().Be(1); + nonSolutionFolderProjects.Single().TypeGuid.Should().Be(expectedTypeGuid); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenPassedAProjectWithoutATypeGuidItErrors(string solutionCommand) + { + var solutionDirectory = _testAssetsManager + .CopyTestAsset("SlnFileWithNoProjectReferencesAndUnknownProjectType", identifier: solutionCommand) + .WithSource() + .Path; + + var solutionPath = Path.Combine(solutionDirectory, "App.sln"); + var contentBefore = File.ReadAllText(solutionPath); + + var projectToAdd = Path.Combine("UnknownProject", "UnknownProject.unknownproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(solutionDirectory) + .Execute(solutionCommand, "add", projectToAdd); + cmd.Should().Pass(); + cmd.StdErr.Should().Be( + string.Format( + CommonLocalizableStrings.UnsupportedProjectType, + Path.Combine(solutionDirectory, projectToAdd))); + cmd.StdOut.Should().BeEmpty(); + + File.ReadAllText(solutionPath) + .Should() + .BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSlnContainsSolutionFolderWithDifferentCasingItDoesNotCreateDuplicate(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCaseSensitiveSolutionFolders", identifier: solutionCommand) + .WithSource() + .Path; + + var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", projectToAdd); + cmd.Should().Pass(); + + var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); + var solutionFolderProjects = slnFile.Projects.Where( + p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid); + solutionFolderProjects.Count().Should().Be(1); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenProjectWithoutMatchingConfigurationsIsAddedSolutionMapsToFirstAvailable(string solutionCommand) + { + var slnDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndProjectConfigs", identifier: solutionCommand) + .WithSource() + .Path; + + var slnFullPath = Path.Combine(slnDirectory, "App.sln"); + + var result = new DotnetCommand(Log) + .WithWorkingDirectory(slnDirectory) + .Execute(solutionCommand, "add", "ProjectWithoutMatchingConfigs"); + result.Should().Pass(); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(ExpectedSlnFileAfterAddingProjectWithoutMatchingConfigs); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenProjectWithMatchingConfigurationsIsAddedSolutionMapsAll(string solutionCommand) + { + var slnDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndProjectConfigs", identifier: solutionCommand) + .WithSource() + .Path; + + var slnFullPath = Path.Combine(slnDirectory, "App.sln"); + + var result = new DotnetCommand(Log) + .WithWorkingDirectory(slnDirectory) + .Execute(solutionCommand, "add", "ProjectWithMatchingConfigs"); + result.Should().Pass(); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(ExpectedSlnFileAfterAddingProjectWithMatchingConfigs); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenProjectWithAdditionalConfigurationsIsAddedSolutionDoesNotMapThem(string solutionCommand) + { + var slnDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndProjectConfigs", identifier: solutionCommand) + .WithSource() + .Path; + + var slnFullPath = Path.Combine(slnDirectory, "App.sln"); + + var result = new DotnetCommand(Log) + .WithWorkingDirectory(slnDirectory) + .Execute(solutionCommand, "add", "ProjectWithAdditionalConfigs"); + result.Should().Pass(); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(ExpectedSlnFileAfterAddingProjectWithAdditionalConfigs); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void ItAddsACSharpProjectThatIsMultitargeted(string solutionCommand) + { + var solutionDirectory = _testAssetsManager + .CopyTestAsset("TestAppsWithSlnAndMultitargetedProjects", identifier: solutionCommand) + .WithSource() + .Path; + + var projectToAdd = Path.Combine("MultitargetedCS", "MultitargetedCS.csproj"); + + new DotnetCommand(Log) + .WithWorkingDirectory(solutionDirectory) + .Execute(solutionCommand, "add", projectToAdd) + .Should() + .Pass() + .And + .HaveStdOutContaining(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectToAdd)); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void ItAddsAVisualBasicProjectThatIsMultitargeted(string solutionCommand) + { + var solutionDirectory = _testAssetsManager + .CopyTestAsset("TestAppsWithSlnAndMultitargetedProjects", identifier: solutionCommand) + .WithSource() + .Path; + + var projectToAdd = Path.Combine("MultitargetedVB", "MultitargetedVB.vbproj"); + + new DotnetCommand(Log) + .WithWorkingDirectory(solutionDirectory) + .Execute(solutionCommand, "add", projectToAdd) + .Should() + .Pass() + .And + .HaveStdOutContaining(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectToAdd)); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void ItAddsAnFSharpProjectThatIsMultitargeted(string solutionCommand) + { + var solutionDirectory = _testAssetsManager + .CopyTestAsset("TestAppsWithSlnAndMultitargetedProjects", identifier: solutionCommand) + .WithSource() + .Path; + + var projectToAdd = Path.Combine("MultitargetedFS", "MultitargetedFS.fsproj"); + + new DotnetCommand(Log) + .WithWorkingDirectory(solutionDirectory) + .Execute(solutionCommand, "add", projectToAdd) + .Should() + .Pass() + .And + .HaveStdOutContaining(string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectToAdd)); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNestedProjectIsAddedAndInRootOptionIsPassedNoSolutionFoldersAreCreated(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "--in-root", projectToAdd); + cmd.Should().Pass(); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingProjectWithInRootOption); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionFolderIsPassedProjectsAreAddedThere(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "--solution-folder", "TestFolder", projectToAdd); + cmd.Should().Pass(); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingProjectWithSolutionFolderOption); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionFolderAndInRootIsPassedItFails(string solutionCommand) + { + var solutionDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + var solutionPath = Path.Combine(solutionDirectory, "App.sln"); + var contentBefore = File.ReadAllText(solutionPath); + + var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(solutionDirectory) + .Execute(solutionCommand, "App.sln", "add", "--solution-folder", "blah", "--in-root", projectToAdd); + cmd.Should().Fail(); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); + cmd.StdErr.Should().Be(Tools.Sln.LocalizableStrings.SolutionFolderAndInRootMutuallyExclusive); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); + + File.ReadAllText(solutionPath) + .Should() + .BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln", "/TestFolder//", "ForwardSlash")] + [InlineData("sln", "\\TestFolder\\\\", "BackwardSlash")] + [InlineData("solution", "/TestFolder//", "ForwardSlash")] + [InlineData("solution", "\\TestFolder\\\\", "BackwardSlash")] + public void WhenSolutionFolderIsPassedWithDirectorySeparatorFolderStructureIsCorrect(string solutionCommand, string solutionFolder, string testIdentifier) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: $"{solutionCommand}{testIdentifier}") + .WithSource() + .Path; + + var projectToAdd = Path.Combine("src", "Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "--solution-folder", solutionFolder, projectToAdd); + cmd.Should().Pass(); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingProjectWithSolutionFolderOption); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + private static string GetExpectedSlnContents( + string slnPath, + string slnTemplate, + string expectedLibProjectGuid = null) + { + var slnFile = SlnFile.Read(slnPath); + + if (string.IsNullOrEmpty(expectedLibProjectGuid)) + { + var matchingProjects = slnFile.Projects + .Where((p) => p.FilePath.EndsWith("Lib.csproj")) + .ToList(); + + matchingProjects.Count.Should().Be(1); + var slnProject = matchingProjects[0]; + expectedLibProjectGuid = slnProject.Id; + } + var slnContents = slnTemplate.Replace("__LIB_PROJECT_GUID__", expectedLibProjectGuid); + + var matchingSrcFolder = slnFile.Projects + .Where((p) => p.FilePath == "src") + .ToList(); + if (matchingSrcFolder.Count == 1) + { + slnContents = slnContents.Replace("__SRC_FOLDER_GUID__", matchingSrcFolder[0].Id); + } + + var matchingSolutionFolder = slnFile.Projects + .Where((p) => p.FilePath == "TestFolder") + .ToList(); + if (matchingSolutionFolder.Count == 1) + { + slnContents = slnContents.Replace("__SOLUTION_FOLDER_GUID__", matchingSolutionFolder[0].Id); + } + + return slnContents; + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionIsPassedAsProjectItPrintsSuggestionAndUsage(string solutionCommand) + { + VerifySuggestionAndUsage(solutionCommand, ""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionIsPassedAsProjectWithInRootItPrintsSuggestionAndUsage(string solutionCommand) + { + VerifySuggestionAndUsage(solutionCommand, "--in-root"); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionIsPassedAsProjectWithSolutionFolderItPrintsSuggestionAndUsage(string solutionCommand) + { + VerifySuggestionAndUsage(solutionCommand, "--solution-folder", "TestFolder"); + } + + private void VerifySuggestionAndUsage(string solutionCommand, params string[] arguments) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}{arguments}") + .WithSource() + .Path; + + var projectArg = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute([solutionCommand, "add", ..arguments, "App.sln", projectArg]); + cmd.Should().Fail(); + cmd.StdErr.Should().BeVisuallyEquivalentTo( + string.Format(CommonLocalizableStrings.SolutionArgumentMisplaced, "App.sln") + Environment.NewLine + + CommonLocalizableStrings.DidYouMean + Environment.NewLine + + (arguments.Length == 0 + ? $" dotnet solution App.sln add {projectArg}" + : $" dotnet solution App.sln add {string.Join(" ", arguments)} {projectArg}") + ); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(""); + } +} diff --git a/test/dotnet-sln.Tests/add/GivenDotnetSlnAddFile.cs b/test/dotnet-sln.Tests/add/GivenDotnetSlnAddFile.cs new file mode 100644 index 000000000000..d6824e4cdb18 --- /dev/null +++ b/test/dotnet-sln.Tests/add/GivenDotnetSlnAddFile.cs @@ -0,0 +1,907 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.Cli.Sln.Internal; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools; +using Microsoft.DotNet.Tools.Common; + +namespace Microsoft.DotNet.Cli.Sln.Add.Tests; + +public class GivenDotnetSlnAddFile(ITestOutputHelper log) : SdkTest(log) +{ + private const string DefaultSolutionFolderName = "Solution Items"; + private Func HelpText = (defaultVal) => $@"Description: + Add one or more solution items to a solution file. + +Usage: + dotnet solution [] add [...] file ... [options] + +Arguments: + The solution file to operate on. If not specified, the command will search the current directory for one. [default: {PathUtility.EnsureTrailingSlash(defaultVal)}] + The paths to the projects to add to the solution. + The paths to the solution items to add to the solution. + +Options: + --in-root Place file in root of the solution, rather than creating a solution folder. + -s, --solution-folder The destination solution folder path to add the files to. + -?, -h, --help Show command line help."; + + private const string ExpectedSlnFileAfterAddingNestedFile = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""Solution Items"", ""Solution Items"", ""__DEFAULT_SOLUTION_FOLDER_GUID__"" + ProjectSection(SolutionItems) = preProject + __NESTED_FILE_PATH__ = __NESTED_FILE_PATH__ + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingFileWithSolutionFolderOption = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""TestFolder"", ""TestFolder"", ""__SOLUTION_FOLDER_GUID__"" + ProjectSection(SolutionItems) = preProject + src\Lib\README = src\Lib\README + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingFilesWithSolutionFolderOption = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""TestFolder"", ""TestFolder"", ""__SOLUTION_FOLDER_GUID__"" + ProjectSection(SolutionItems) = preProject + src\README = src\README + src\Lib\README = src\Lib\README + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingFileToNestedSolutionFolder = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""TestFolder"", ""TestFolder"", ""__SOLUTION_FOLDER_GUID__"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""InnerTestFolder"", ""InnerTestFolder"", ""__INNER_SOLUTION_FOLDER_GUID__"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""InnermostTestFolder"", ""InnermostTestFolder"", ""__INNERMOST_SOLUTION_FOLDER_GUID__"" + ProjectSection(SolutionItems) = preProject + src\Lib\README = src\Lib\README + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + __INNER_SOLUTION_FOLDER_GUID__ = __SOLUTION_FOLDER_GUID__ + __INNERMOST_SOLUTION_FOLDER_GUID__ = __INNER_SOLUTION_FOLDER_GUID__ + EndGlobalSection +EndGlobal +"; + + [Theory] + [InlineData("sln", "--help")] + [InlineData("sln", "-h")] + [InlineData("sln", "-?")] + [InlineData("sln", "/?")] + [InlineData("solution", "--help")] + [InlineData("solution", "-h")] + [InlineData("solution", "-?")] + [InlineData("solution", "/?")] + public void WhenHelpOptionIsPassedItPrintsUsage(string solutionCommand, string helpArg) + { + var cmd = new DotnetCommand(Log) + .Execute(solutionCommand, "add", "file", helpArg); + cmd.Should().Pass() + .And.HaveStdErr(""); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(HelpText(Directory.GetCurrentDirectory())); + } + + [Theory] + [InlineData("sln", "")] + [InlineData("sln", "unknownCommandName")] + [InlineData("solution", "")] + [InlineData("solution", "unknownCommandName")] + public void WhenNoCommandIsPassedItPrintsError(string solutionCommand, string commandName) + { + var cmd = new DotnetCommand(Log) + .Execute($"{solutionCommand} {commandName}".Trim().Split()); + cmd.Should().Fail() + .And.HaveStdErr(CommonLocalizableStrings.RequiredCommandNotPassed); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenTooManyArgumentsArePassedItPrintsError(string solutionCommand) + { + var cmd = new DotnetCommand(Log) + .Execute(solutionCommand, "one.sln", "two.sln", "three.sln", "add", "file", "README"); + cmd.Should().Fail(); + cmd.StdErr.Should().BeVisuallyEquivalentTo($@"{string.Format(CommandLineValidation.LocalizableStrings.UnrecognizedCommandOrArgument, "two.sln")} +{string.Format(CommandLineValidation.LocalizableStrings.UnrecognizedCommandOrArgument, "three.sln")}"); + } + + [Theory] + [InlineData("sln", "idontexist.sln")] + [InlineData("sln", "ihave?invalidcharacters")] + [InlineData("sln", "ihaveinv@lidcharacters")] + [InlineData("sln", "ihaveinvalid/characters")] + [InlineData("sln", "ihaveinvalidchar\\acters")] + [InlineData("solution", "idontexist.sln")] + [InlineData("solution", "ihave?invalidcharacters")] + [InlineData("solution", "ihaveinv@lidcharacters")] + [InlineData("solution", "ihaveinvalid/characters")] + [InlineData("solution", "ihaveinvalidchar\\acters")] + public void WhenNonExistingSolutionIsPassedItPrintsErrorAndUsage(string solutionCommand, string solutionName) + { + var cmd = new DotnetCommand(Log) + .Execute(solutionCommand, solutionName, "add", "file", "README"); + cmd.Should().Fail() + .And.HaveStdOut("") + .And.HaveStdErr(string.Format(CommonLocalizableStrings.CouldNotFindSolutionOrDirectory, solutionName)); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenInvalidSolutionIsPassedItPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("InvalidSolution", identifier: solutionCommand) + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "InvalidSolution.sln", "add", "file", "README"); + cmd.Should().Fail() + .And.HaveStdOut("") + .And.HaveStdErr(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, "InvalidSolution.sln", LocalizableStrings.FileHeaderMissingError)); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenInvalidSolutionIsFoundAddPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("InvalidSolution", identifier: solutionCommand) + .WithSource() + .Path; + + var solutionPath = Path.Combine(projectDirectory, "InvalidSolution.sln"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "add", "file", "README"); + cmd.Should().Fail() + .And.HaveStdOut("") + .And.HaveStdErr(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, solutionPath, LocalizableStrings.FileHeaderMissingError)); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNoFileIsPassedItPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file"); + cmd.Should().Fail() + .And.HaveStdErr("Required argument missing for command: 'file'."); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(HelpText(projectDirectory)); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNoSolutionExistsInTheDirectoryAddPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var solutionPath = Path.Combine(projectDirectory, "App"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(solutionPath) + .Execute(solutionCommand, "add", "file", "README"); + cmd.Should().Fail() + .And.HaveStdErr(string.Format(CommonLocalizableStrings.SolutionDoesNotExist, solutionPath + Path.DirectorySeparatorChar)) + .And.HaveStdOut(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenMoreThanOneSolutionExistsInTheDirectoryItPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithMultipleSlnFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "add", "file", "README"); + cmd.Should().Fail() + .And.HaveStdOut("") + .And.HaveStdErr(string.Format(CommonLocalizableStrings.MoreThanOneSolutionInDirectory, projectDirectory + Path.DirectorySeparatorChar)); + } + + [Theory] + [InlineData("sln", "App/App.csproj", "noPeriod")] + [InlineData("solution", "App/App.csproj", "noPeriod")] + [InlineData("sln", "./App/App.csproj", "hasPeriod")] + [InlineData("solution", "./App/App.csproj", "hasPeriod")] + public void WhenExistingProjectIsPassedAsSolutionItemItFails(string solutionCommand, string relativePathToExistingProjectFile, string identifier) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}{identifier}") + .WithSource() + .Path; + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnPath); + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", relativePathToExistingProjectFile); + cmd.Should().Fail() + .And.HaveStdOut("") + .And.HaveStdErr(Tools.Sln.LocalizableStrings.CannotAddExistingProjectAsSolutionItem); + + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenTheSameSolutionIsPassedAsSolutionItemItFails(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("SlnFileWithSolutionItemsInNestedFolders", identifier: solutionCommand) + .WithSource() + .Path; + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnPath); + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", "App.sln"); + cmd.Should().Fail() + .And.HaveStdOut("") + .And.HaveStdErr(Tools.Sln.LocalizableStrings.CannotAddTheSameSolutionToItself); + + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenExistingSolutionItemIsPassedItFails(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("SlnFileWithSolutionItemsInNestedFolders", identifier: solutionCommand) + .WithSource() + .Path; + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnPath); + + const string fileToAdd = "TextFile1.txt"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", fileToAdd); + cmd.Should().Fail() + .And.HaveStdOut("") + .And.HaveStdErr(string.Format(Tools.Sln.LocalizableStrings.SolutionItemWithTheSameNameExists, fileToAdd, "NewFolder2")); + + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNestedFileIsAddedSolutionFoldersAreCreated(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndReadmeInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + var fileToAdd = Path.Combine("src", "Lib", "README"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", fileToAdd); + cmd.Should().Pass() + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd, DefaultSolutionFolderName)) + .And.HaveStdErr(""); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingNestedFile, nestedFilePath: fileToAdd); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNestedSolutionItemsAreAddedToASolutionFolder(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndReadmeInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + string fileToAdd; + CommandResult cmd; + + fileToAdd = Path.Combine("src", "Lib", "README"); + cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", fileToAdd); + cmd.Should().Pass() + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd, DefaultSolutionFolderName)) + .And.HaveStdErr(""); + + fileToAdd = Path.Combine("src", "README"); + cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", fileToAdd); + cmd.Should().Pass() + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd, DefaultSolutionFolderName)) + .And.HaveStdErr(""); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] + [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("sln", "TestAppWithEmptySln")] + [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] + [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("solution", "TestAppWithEmptySln")] + public void WhenValidFileIsPassedItGetsAdded(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + var fileToAdd = Path.Combine("Empty", "README"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", fileToAdd); + cmd.Should().Pass() + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd, DefaultSolutionFolderName)) + .And.HaveStdErr(""); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] + [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("sln", "TestAppWithEmptySln")] + [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] + [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("solution", "TestAppWithEmptySln")] + public void WhenValidFileInSolutionRootIsPassedItGetsAdded(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", "README"); + cmd.Should().Pass() + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, "README", DefaultSolutionFolderName)) + .And.HaveStdErr(""); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] + [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("sln", "TestAppWithEmptySln")] + [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] + [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("solution", "TestAppWithEmptySln")] + public void WhenValidFileInFolderIsPassedItGetsAdded(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", "README"); + cmd.Should().Pass() + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, "README", DefaultSolutionFolderName)) + .And.HaveStdErr(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenFileIsAddedSolutionHasUTF8BOM(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithEmptySln", identifier: solutionCommand) + .WithSource() + .Path; + + var fileToAdd = Path.Combine("Empty", "README"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", fileToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd, DefaultSolutionFolderName)); + + var preamble = Encoding.UTF8.GetPreamble(); + preamble.Length.Should().Be(3); + using var stream = new FileStream(Path.Combine(projectDirectory, "App.sln"), FileMode.Open); + var bytes = new byte[preamble.Length]; +#pragma warning disable CA2022 // Avoid inexact read + stream.Read(bytes, 0, bytes.Length); +#pragma warning restore CA2022 // Avoid inexact read + bytes.Should().BeEquivalentTo(preamble); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndExistingCsprojReferences")] + [InlineData("sln", "TestAppWithSlnAndExistingCsprojReferencesWithEscapedDirSep")] + [InlineData("solution", "TestAppWithSlnAndExistingCsprojReferences")] + [InlineData("solution", "TestAppWithSlnAndExistingCsprojReferencesWithEscapedDirSep")] + public void WhenSolutionAlreadyContainsFileItDoesntDuplicate(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory); + + const string fileToAdd = "README"; + cmd.Execute(solutionCommand, "App.sln", "add", "file", fileToAdd) + .Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd, DefaultSolutionFolderName)); + cmd.Execute(solutionCommand, "App.sln", "add", "file", fileToAdd) + .Should().Fail() + .And.HaveStdOut("") + .And.HaveStdErr(string.Format(Tools.Sln.LocalizableStrings.SolutionItemWithTheSameNameExists, fileToAdd, DefaultSolutionFolderName)); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenPassedMultipleFilesAndOneOfthemDoesNotExistItCancelsWholeOperation(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var slnFullPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnFullPath); + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", "README", "idonotexist.txt"); + cmd.Should().Fail() + .And.HaveStdErr(string.Format(Tools.Sln.LocalizableStrings.CouldNotFindFile, "idonotexist.txt")) + .And.HaveStdOut(""); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenProjectPathIsPassedItFails(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var slnFullPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnFullPath); + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "Lib/Lib.csproj", "file", "README"); + cmd.Should().Fail() + .And.HaveStdErr(Tools.Sln.LocalizableStrings.ProjectPathArgumentShouldNotBeProvidedForDotnetSlnAddFile) + .And.HaveStdOut(""); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenPassedAlreadyAddedProjectFileItFails(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var slnFullPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnFullPath); + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", "App/App.csproj"); + cmd.Should().Fail() + .And.HaveStdErr(Tools.Sln.LocalizableStrings.CannotAddExistingProjectAsSolutionItem) + .And.HaveStdOut(""); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSlnContainsSolutionFolderWithDifferentCasingItDoesNotCreateDuplicate(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCaseSensitiveSolutionFolders", identifier: solutionCommand) + .WithSource() + .Path; + + var fileToAdd = Path.Combine("src", "App", "README"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", fileToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd, DefaultSolutionFolderName)); + + var slnFilePath = Path.Combine(projectDirectory, "App.sln"); + var slnFile = SlnFile.Read(slnFilePath); + var solutionFolderProjects = slnFile.Projects.Where( + p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid); + solutionFolderProjects.Count().Should().Be(2); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionFolderIsPassedFileIsAddedThere(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndReadmeInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + var fileToAdd = Path.Combine("src", "Lib", "README"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", "--solution-folder", "TestFolder", fileToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd, "TestFolder")); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingFileWithSolutionFolderOption, solutionFolderName: "TestFolder"); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionFolderIsPassedFilesAreAddedThere2(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndReadmeInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + var fileToAdd1 = Path.Combine("src", "README"); + var fileToAdd2 = Path.Combine("src", "Lib", "README"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", "--solution-folder", "TestFolder", fileToAdd1, fileToAdd2); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOutContaining(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd1, "TestFolder")) + .And.HaveStdOutContaining(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd2, "TestFolder")); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingFilesWithSolutionFolderOption, solutionFolderName: "TestFolder"); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln", "TestFolder/InnerTestFolder/InnermostTestFolder", "ForwardSlash")] + [InlineData("solution", "TestFolder/InnerTestFolder/InnermostTestFolder", "ForwardSlash")] + [InlineData("sln", @"TestFolder\InnerTestFolder\InnermostTestFolder", "BackwardSlash")] + [InlineData("solution", @"TestFolder\InnerTestFolder\InnermostTestFolder", "BackwardSlash")] + public void WhenNestedSolutionFolderIsPassedButDoesNotExistTheSolutionFolderIsCreatedAndTheSolutionItemIsAdded(string solutionCommand, string solutionFolder, string slash) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndReadmeInSubDir", identifier: $"{solutionCommand}{slash}") + .WithSource() + .Path; + + var fileToAdd = Path.Combine("src", "Lib", "README"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", "--solution-folder", solutionFolder, fileToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + //.And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd, "InnermostTestFolder")) + ; + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingFileToNestedSolutionFolder, solutionFolderName: "TestFolder"); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln", "TestFolder/InnerTestFolder/InnermostTestFolder", "ForwardSlash")] + [InlineData("solution", "TestFolder/InnerTestFolder/InnermostTestFolder", "ForwardSlash")] + [InlineData("sln", @"TestFolder\InnerTestFolder\InnermostTestFolder", "BackwardSlash")] + [InlineData("solution", @"TestFolder\InnerTestFolder\InnermostTestFolder", "BackwardSlash")] + public void WhenNestedSolutionFolderIsPassedAndDoesExistTheSolutionItemIsAdded(string solutionCommand, string solutionFolder, string slash) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnContainingNestedSolutionFolder", identifier: $"{solutionCommand}{slash}") + .WithSource() + .Path; + + var fileToAdd = Path.Combine("src", "Lib", "README"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", "--solution-folder", solutionFolder, fileToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + //.And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd, "InnermostTestFolder")) + ; + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingFileToNestedSolutionFolder, solutionFolderName: "TestFolder"); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionFolderAndInRootIsPassedItFails(string solutionCommand) + { + var solutionDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndReadmeInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + var solutionPath = Path.Combine(solutionDirectory, "App.sln"); + var contentBefore = File.ReadAllText(solutionPath); + + var fileToAdd = Path.Combine("src", "Lib", "README"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(solutionDirectory) + .Execute(solutionCommand, "App.sln", "add", "--solution-folder", "blah", "--in-root", fileToAdd); + cmd.Should().Fail() + .And.HaveStdOut("") + .And.HaveStdErr(Tools.Sln.LocalizableStrings.SolutionFolderAndInRootMutuallyExclusive); + + File.ReadAllText(solutionPath) + .Should() + .BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln", "/TestFolder//", "ForwardSlash")] + [InlineData("sln", "\\TestFolder\\\\", "BackwardSlash")] + [InlineData("solution", "/TestFolder//", "ForwardSlash")] + [InlineData("solution", "\\TestFolder\\\\", "BackwardSlash")] + public void WhenSolutionFolderIsPassedWithDirectorySeparatorFolderStructureIsCorrect(string solutionCommand, string solutionFolder, string testIdentifier) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndReadmeInSubDir", identifier: $"{solutionCommand}{testIdentifier}") + .WithSource() + .Path; + + var fileToAdd = Path.Combine("src", "Lib", "README"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "file", "--solution-folder", solutionFolder, fileToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionItemAddedToTheSolution, fileToAdd, solutionFolder.Trim('/', '\\'))); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingFileWithSolutionFolderOption, solutionFolderName: solutionFolder); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + private static string GetExpectedSlnContents( + string slnPath, + string slnTemplate, + string solutionFolderName = "Solution Items", + string nestedFilePath = null) + { + var slnFile = SlnFile.Read(slnPath); + + var matchingProjects = slnFile.Projects + .Where(p => p.FilePath == solutionFolderName.Trim('/', '\\')) + .ToList(); + + matchingProjects.Count.Should().Be(1); + var slnProject = matchingProjects[0]; + + var slnContents = slnTemplate.Replace("__DEFAULT_SOLUTION_FOLDER_GUID__", slnProject.Id); + + if (nestedFilePath != null) + { + slnContents = slnContents.Replace("__NESTED_FILE_PATH__", nestedFilePath); + } + + var matchingSolutionFolder = slnFile.Projects + .Where((p) => p.FilePath == "TestFolder") + .ToList(); + if (matchingSolutionFolder.Count == 1) + { + slnContents = slnContents.Replace("__SOLUTION_FOLDER_GUID__", matchingSolutionFolder[0].Id); + } + + var matchingInnerSolutionFolder = slnFile.Projects + .Where((p) => p.FilePath == "InnerTestFolder") + .ToList(); + if (matchingInnerSolutionFolder.Count == 1) + { + slnContents = slnContents.Replace("__INNER_SOLUTION_FOLDER_GUID__", matchingInnerSolutionFolder[0].Id); + } + + var matchingInnermostSolutionFolder = slnFile.Projects + .Where((p) => p.FilePath == "InnermostTestFolder") + .ToList(); + if (matchingInnermostSolutionFolder.Count == 1) + { + slnContents = slnContents.Replace("__INNERMOST_SOLUTION_FOLDER_GUID__", matchingInnermostSolutionFolder[0].Id); + } + + return slnContents; + } +} diff --git a/test/dotnet-sln.Tests/add/GivenDotnetSlnAddFolder.cs b/test/dotnet-sln.Tests/add/GivenDotnetSlnAddFolder.cs new file mode 100644 index 000000000000..58693046cfb0 --- /dev/null +++ b/test/dotnet-sln.Tests/add/GivenDotnetSlnAddFolder.cs @@ -0,0 +1,819 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.Cli.Sln.Internal; +using Microsoft.DotNet.Tools; +using Microsoft.DotNet.Tools.Common; + +namespace Microsoft.DotNet.Cli.Sln.Add.Tests; + +public class GivenDotnetSlnAddFolder(ITestOutputHelper log) : SdkTest(log) +{ + private readonly Func HelpText = (defaultVal) => $@"Description: + Add one or more solution folders to a solution file. + +Usage: + dotnet solution [] add [...] folder ... [options] + +Arguments: + The solution file to operate on. If not specified, the command will search the current directory for one. [default: {PathUtility.EnsureTrailingSlash(defaultVal)}] + The paths to the projects to add to the solution. + The paths to the solution folders to add to the solution. + +Options: + --in-root Place folder in root of the solution, rather than creating a solution folder. + -s, --solution-folder The destination solution folder path to add the folders to. + -?, -h, --help Show command line help."; + + private const string ExpectedSlnFileAfterAddingSolutionFolderWithInRootOption = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""src"", ""src"", ""__SRC_FOLDER_GUID__"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""Empty"", ""Empty"", ""__EMPTY_FOLDER_GUID__"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingSolutionFolderWithSolutionFolderOption = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""TestFolder"", ""TestFolder"", ""__SOLUTION_FOLDER_GUID__"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""src"", ""src"", ""__SRC_FOLDER_GUID__"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + __SRC_FOLDER_GUID__ = __SOLUTION_FOLDER_GUID__ + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingSolutionFoldersWithSolutionFolderOption = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""TestFolder"", ""TestFolder"", ""__SOLUTION_FOLDER_GUID__"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""src"", ""src"", ""__SRC_FOLDER_GUID__"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""Empty"", ""Empty"", ""__EMPTY_FOLDER_GUID__"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + __SRC_FOLDER_GUID__ = __SOLUTION_FOLDER_GUID__ + __EMPTY_FOLDER_GUID__ = __SOLUTION_FOLDER_GUID__ + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingSolutionFoldersWithNestedSolutionFolderOption = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""TestFolder"", ""TestFolder"", ""__SOLUTION_FOLDER_GUID__"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""src"", ""src"", ""__SRC_FOLDER_GUID__"" +EndProject +Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""Lib"", ""Lib"", ""__LIB_FOLDER_GUID__"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + __SRC_FOLDER_GUID__ = __SOLUTION_FOLDER_GUID__ + __LIB_FOLDER_GUID__ = __SRC_FOLDER_GUID__ + EndGlobalSection +EndGlobal +"; + + [Theory] + [InlineData("sln", "--help")] + [InlineData("sln", "-h")] + [InlineData("sln", "-?")] + [InlineData("sln", "/?")] + [InlineData("solution", "--help")] + [InlineData("solution", "-h")] + [InlineData("solution", "-?")] + [InlineData("solution", "/?")] + public void WhenHelpOptionIsPassedItPrintsUsage(string solutionCommand, string helpArg) + { + var cmd = new DotnetCommand(Log) + .Execute(solutionCommand, "add", "folder", helpArg); + cmd.Should().Pass(); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(HelpText(Directory.GetCurrentDirectory())); + } + + [Theory] + [InlineData("sln", "")] + [InlineData("sln", "unknownCommandName")] + [InlineData("solution", "")] + [InlineData("solution", "unknownCommandName")] + public void WhenNoCommandIsPassedItPrintsError(string solutionCommand, string commandName) + { + var cmd = new DotnetCommand(Log) + .Execute($"{solutionCommand} {commandName}".Trim().Split()); + cmd.Should().Fail() + .And.HaveStdErr(CommonLocalizableStrings.RequiredCommandNotPassed); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenTooManyArgumentsArePassedItPrintsError(string solutionCommand) + { + var cmd = new DotnetCommand(Log) + .Execute(solutionCommand, "one.sln", "two.sln", "three.sln", "add", "folder", "myFolder"); + cmd.Should().Fail(); + cmd.StdErr.Should().BeVisuallyEquivalentTo($@"{string.Format(CommandLineValidation.LocalizableStrings.UnrecognizedCommandOrArgument, "two.sln")} +{string.Format(CommandLineValidation.LocalizableStrings.UnrecognizedCommandOrArgument, "three.sln")}"); + } + + [Theory] + [InlineData("sln", "idontexist.sln")] + [InlineData("sln", "ihave?invalidcharacters")] + [InlineData("sln", "ihaveinv@lidcharacters")] + [InlineData("sln", "ihaveinvalid/characters")] + [InlineData("sln", "ihaveinvalidchar\\acters")] + [InlineData("solution", "idontexist.sln")] + [InlineData("solution", "ihave?invalidcharacters")] + [InlineData("solution", "ihaveinv@lidcharacters")] + [InlineData("solution", "ihaveinvalid/characters")] + [InlineData("solution", "ihaveinvalidchar\\acters")] + public void WhenNonExistingSolutionIsPassedItPrintsErrorAndUsage(string solutionCommand, string solutionName) + { + var cmd = new DotnetCommand(Log) + .Execute(solutionCommand, solutionName, "add", "folder", "myFolder"); + cmd.Should().Fail() + .And.HaveStdErr(string.Format(CommonLocalizableStrings.CouldNotFindSolutionOrDirectory, solutionName)) + .And.HaveStdOut(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenInvalidSolutionIsPassedItPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("InvalidSolution", identifier: solutionCommand) + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "InvalidSolution.sln", "add", "folder", "MyFolder"); + cmd.Should().Fail() + .And.HaveStdErr(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, "InvalidSolution.sln", LocalizableStrings.FileHeaderMissingError)) + .And.HaveStdOut(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenInvalidSolutionIsFoundAddPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("InvalidSolution", identifier: solutionCommand) + .WithSource() + .Path; + + var solutionPath = Path.Combine(projectDirectory, "InvalidSolution.sln"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "add", "folder", "MyFolder"); + cmd.Should().Fail() + .And.HaveStdErr(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, solutionPath, LocalizableStrings.FileHeaderMissingError)) + .And.HaveStdOut(""); + } + + + [Theory] + [ClassData(typeof(InvalidSolutionFolderNameTheoryData))] + + public void WhenInvalidSolutionFolderNameIsNoFolderIsPassedItPrintsErrorAndUsage(string solutionCommand, string invalidSolutionFolderName, string identifier) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}{identifier}") + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", invalidSolutionFolderName); + cmd.Should().Fail() + .And.HaveStdErrContaining("Solution Folder names cannot:") + .And.HaveStdOut(""); + } + + private class InvalidSolutionFolderNameTheoryData : TheoryData + { + public InvalidSolutionFolderNameTheoryData() + { + string[] solutionCommands = ["sln", "solution"]; + Dictionary invalidFolderNames = new() + { + // invalid characters + { "/", "ForwardSlash" }, + { ":", "Colon" }, + { "?", "QuestionMark" }, + { "\\", "BackwardSlash" }, + { "*", "Asterisk" }, + { "\"", "DoubleQuotationMark" }, + { "<", "LessThan" }, + { ">", "GreaterThan" }, + { "|", "VerticalBar" }, + // Unicode control characters + { "\u001B", "UnicodeEscapeCharacter" }, + // surrogate characters + { "\uD834\uDD1E", "TrebleClef" }, // the musical symbol 𝄞 (U+1D11E) + // system reserved names + { "CON", "CON" }, + { "AUX", "AUX" }, + { "PRN", "PRN" }, + { "COM1", "COM1" }, + { "LPT2", "LPT2" }, + }; + + foreach (var solutionCommand in solutionCommands) + { + foreach (var (invalidFolderName, identifier) in invalidFolderNames) + { + Add(solutionCommand, invalidFolderName, identifier); + } + } + } + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNoFolderIsPassedItPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder"); + cmd.Should().Fail() + .And.HaveStdErr("Required argument missing for command: 'folder'."); + cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(HelpText(projectDirectory)); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNoSolutionExistsInTheDirectoryAddPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var solutionPath = Path.Combine(projectDirectory, "App"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(solutionPath) + .Execute(solutionCommand, "add", "folder", "App"); + cmd.Should().Fail() + .And.HaveStdErr(string.Format(CommonLocalizableStrings.SolutionDoesNotExist, solutionPath + Path.DirectorySeparatorChar)) + .And.HaveStdOut(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenMoreThanOneSolutionExistsInTheDirectoryItPrintsErrorAndUsage(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithMultipleSlnFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "add", "folder", "folderToAdd"); + cmd.Should().Fail() + .And.HaveStdErr(string.Format(CommonLocalizableStrings.MoreThanOneSolutionInDirectory, projectDirectory + Path.DirectorySeparatorChar)) + .And.HaveStdOut(""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenProjectPathIsPassedItFails(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: solutionCommand) + .WithSource() + .Path; + + var slnFullPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnFullPath); + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "Lib/Lib.csproj", "folder", "NewSolutionFolder"); + cmd.Should().Fail() + .And.HaveStdErr(Tools.Sln.LocalizableStrings.ProjectPathArgumentShouldNotBeProvidedForDotnetSlnAddFolder) + .And.HaveStdOut(""); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] + [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("sln", "TestAppWithEmptySln")] + [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] + [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("solution", "TestAppWithEmptySln")] + public void WhenValidFolderIsPassedItGetsAdded(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + const string folderToAdd = "Empty"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", folderToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, folderToAdd)); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] + [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("sln", "TestAppWithEmptySln")] + [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] + [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("solution", "TestAppWithEmptySln")] + public void WhenValidSolutionFolderInSolutionRootIsPassedItGetsAdded(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + const string folderToAdd = "MySolutionFolder"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", folderToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, folderToAdd)); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndCsprojFiles")] + [InlineData("sln", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("sln", "TestAppWithEmptySln")] + [InlineData("solution", "TestAppWithSlnAndCsprojFiles")] + [InlineData("solution", "TestAppWithSlnAndCsprojProjectGuidFiles")] + [InlineData("solution", "TestAppWithEmptySln")] + public void WhenValidNestedSolutionFolderIsPassedItGetsAdded(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + const string folderToAdd = "Empty"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", folderToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, folderToAdd)); + } + + [Theory] + [InlineData("sln", "TestAppWithSlnAndSolutionFolders")] + [InlineData("solution", "TestAppWithSlnAndSolutionFolders")] + public void WhenSolutionAlreadyContainsFolderItDoesntDuplicate(string solutionCommand, string testAsset) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + const string folderToAdd = "src"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", folderToAdd); + var slnPath = Path.Combine(projectDirectory, "App.sln"); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionAlreadyContainsFolder, slnPath, folderToAdd)); + } + + [Theory] + [InlineData("sln", "SlnFileWithNoProjectReferencesAndCSharpProject", "CSharpProject", ProjectTypeGuids.SolutionFolderGuid)] + public void WhenPassedAFolderItAddsCorrectProjectTypeGuid( + string solutionCommand, + string testAsset, + string folderToAdd, + string expectedTypeGuid) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset(testAsset, identifier: $"{solutionCommand}{testAsset}") + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", folderToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, folderToAdd)); + + var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); + var solutionFolderProjects = slnFile.Projects.Where( + p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid); + solutionFolderProjects.Count().Should().Be(1); + solutionFolderProjects.Single().TypeGuid.Should().Be(expectedTypeGuid); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSlnContainsSolutionFolderWithDifferentCasingItDoesNotCreateDuplicate(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCaseSensitiveSolutionFolders", identifier: solutionCommand) + .WithSource() + .Path; + + const string folderToAdd = "src"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", "src"); + var slnPath = Path.Combine(projectDirectory, "App.sln"); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionAlreadyContainsFolder, slnPath, folderToAdd)); + + var slnFile = SlnFile.Read(slnPath); + var solutionFolderProjects = slnFile.Projects.Where( + p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid); + solutionFolderProjects.Count().Should().Be(1); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenInRootIsPassedSolutionFoldersAreAddedToTheRoot(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + const string folderToAdd1 = "src"; + const string folderToAdd2 = "Empty"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", "--in-root", folderToAdd1, folderToAdd2); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOutContaining(string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, folderToAdd1)) + .And.HaveStdOutContaining(string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, folderToAdd2)); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingSolutionFolderWithInRootOption); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionFolderIsPassedSolutionFolderIsAddedThere(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + const string folderToAdd = "src"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", "--solution-folder", "TestFolder", folderToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, folderToAdd)); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingSolutionFolderWithSolutionFolderOption); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionFolderIsPassedSolutionFoldersAreAddedThere(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + const string folderToAdd1 = "src"; + const string folderToAdd2 = "Empty"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", "--solution-folder", "TestFolder", folderToAdd1, folderToAdd2); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOutContaining(string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, folderToAdd1)) + .And.HaveStdOutContaining(string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, folderToAdd2)); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingSolutionFoldersWithSolutionFolderOption); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenNestedSolutionFolderIsPassedSolutionFolderIsAddedThere(string solutionCommand) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + const string folderToAdd = "Lib"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", "--solution-folder", "TestFolder/src", folderToAdd); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOutContaining(string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, folderToAdd)); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingSolutionFoldersWithNestedSolutionFolderOption); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionFolderAndInRootIsPassedItFails(string solutionCommand) + { + var solutionDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: solutionCommand) + .WithSource() + .Path; + + var solutionPath = Path.Combine(solutionDirectory, "App.sln"); + var contentBefore = File.ReadAllText(solutionPath); + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(solutionDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", "--solution-folder", "blah", "--in-root", "src"); + cmd.Should().Fail() + .And.HaveStdErr(Tools.Sln.LocalizableStrings.SolutionFolderAndInRootMutuallyExclusive) + .And.HaveStdOut(""); + + File.ReadAllText(solutionPath) + .Should() + .BeVisuallyEquivalentTo(contentBefore); + } + + [Theory] + [InlineData("sln", "/TestFolder//", "ForwardSlash")] + [InlineData("sln", "\\TestFolder\\\\", "BackwardSlash")] + [InlineData("solution", "/TestFolder//", "ForwardSlash")] + [InlineData("solution", "\\TestFolder\\\\", "BackwardSlash")] + public void WhenSolutionFolderIsPassedWithDirectorySeparatorFolderStructureIsCorrect(string solutionCommand, string solutionFolder, string testIdentifier) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojInSubDir", identifier: $"{solutionCommand}{testIdentifier}") + .WithSource() + .Path; + + const string folderToAdd = "src"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "App.sln", "add", "folder", "--solution-folder", solutionFolder, "src"); + cmd.Should().Pass() + .And.HaveStdErr("") + .And.HaveStdOut(string.Format(Tools.Sln.LocalizableStrings.SolutionFolderAddedToTheSolution, folderToAdd)); + + var slnPath = Path.Combine(projectDirectory, "App.sln"); + var expectedSlnContents = GetExpectedSlnContents(slnPath, ExpectedSlnFileAfterAddingSolutionFolderWithSolutionFolderOption); + File.ReadAllText(slnPath) + .Should().BeVisuallyEquivalentTo(expectedSlnContents); + } + + private static string GetExpectedSlnContents( + string slnPath, + string slnTemplate) + { + var slnFile = SlnFile.Read(slnPath); + + var slnContents = slnTemplate; + + var matchingSrcFolder = slnFile.Projects + .Where((p) => p.FilePath == "src") + .ToList(); + if (matchingSrcFolder.Count == 1) + { + slnContents = slnContents.Replace("__SRC_FOLDER_GUID__", matchingSrcFolder[0].Id); + } + + var matchingEmptyFolder = slnFile.Projects + .Where((p) => p.FilePath == "Empty") + .ToList(); + if (matchingEmptyFolder.Count == 1) + { + slnContents = slnContents.Replace("__EMPTY_FOLDER_GUID__", matchingEmptyFolder[0].Id); + } + + var matchingLibFolder = slnFile.Projects + .Where((p) => p.FilePath == "Lib") + .ToList(); + if (matchingLibFolder.Count == 1) + { + slnContents = slnContents.Replace("__LIB_FOLDER_GUID__", matchingLibFolder[0].Id); + } + + var matchingSolutionFolder = slnFile.Projects + .Where((p) => p.FilePath == "TestFolder") + .ToList(); + if (matchingSolutionFolder.Count == 1) + { + slnContents = slnContents.Replace("__SOLUTION_FOLDER_GUID__", matchingSolutionFolder[0].Id); + } + + return slnContents; + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionIsPassedAsFolderItPrintsSuggestionAndUsage(string solutionCommand) + { + VerifySuggestionAndUsage(solutionCommand, ""); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionIsPassedAsFolderWithInRootItPrintsSuggestionAndUsage(string solutionCommand) + { + VerifySuggestionAndUsage(solutionCommand, "--in-root"); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionIsPassedAsFolderWithSolutionFolderItPrintsSuggestionAndUsage(string solutionCommand) + { + VerifySuggestionAndUsage(solutionCommand, "--solution-folder", "TestFolder"); + } + + private void VerifySuggestionAndUsage(string solutionCommand, params string[] arguments) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}{string.Join("", arguments)}") + .WithSource() + .Path; + + const string folderArg = "src"; + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute([solutionCommand, "add", "folder", .. arguments, folderArg, "App.sln"]); + cmd.Should().Fail() + .And.HaveStdOut(""); + cmd.StdErr.Should().BeVisuallyEquivalentTo( + string.Format(CommonLocalizableStrings.SolutionArgumentMisplaced, "App.sln") + Environment.NewLine + + CommonLocalizableStrings.DidYouMean + Environment.NewLine + + (arguments.Length == 0 + ? $" dotnet solution App.sln add folder {folderArg}" + : $" dotnet solution App.sln add folder {string.Join(" ", arguments)} {folderArg}") + ); + } +}