From 6c4ef88828f0c17febae588353653075d244cc8a Mon Sep 17 00:00:00 2001 From: Richard Stanton Date: Fri, 28 Feb 2025 14:52:01 -0800 Subject: [PATCH] Support for project types with no platforms. --- .../Model/ConfigurationRuleFollower.cs | 2 +- .../Model/ModelHelper.cs | 5 ++ .../Model/PlatformNames.cs | 8 +++ .../Model/ProjectType.cs | 6 +-- .../Model/ProjectTypeTable.BuiltInTypes.cs | 3 ++ .../Model/StringTable.cs | 6 +++ .../Serializer/SlnV12/SlnV12Extensions.cs | 25 +++++---- .../Serializer/Xml/Keywords.cs | 4 ++ .../Serializer/Xml/Slnx.xsd | 9 ++-- .../Serializer/Xml/SlnxSerializerSettings.cs | 4 +- .../Serializer/Xml/XmlDecorators/SlnxFile.cs | 2 +- .../XmlDecorators/XmlContainer.ApplyModel.cs | 2 +- .../Xml/XmlDecorators/XmlContainer.cs | 2 +- .../Xml/XmlDecorators/XmlProjectType.cs | 52 ++++++++++++++++++- .../Serialization/RoundTripClassicSln.cs | 3 ++ .../RoundTripClassicSlnThruSlnxStream.cs | 3 ++ .../Serialization/RoundTripXmlSlnx.cs | 3 ++ .../RoundTripXmlSlnxThruModelCopy.cs | 3 ++ .../Serialization/StringTests.cs | 38 ++++++++++++-- .../BuiltInProjectTypes.slnx.xml | 1 + .../MissingConfigurations.sln.txt | 2 +- .../SlnAssets/Report Project.sln.txt | 30 +++++++++++ .../SlnAssets/Report Project.slnx.xml | 12 +++++ 23 files changed, 194 insertions(+), 31 deletions(-) create mode 100644 test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Report Project.sln.txt create mode 100644 test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Report Project.slnx.xml diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Model/ConfigurationRuleFollower.cs b/src/Microsoft.VisualStudio.SolutionPersistence/Model/ConfigurationRuleFollower.cs index 6d65615a..cf13facd 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Model/ConfigurationRuleFollower.cs +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Model/ConfigurationRuleFollower.cs @@ -30,7 +30,7 @@ internal readonly ref struct ConfigurationRuleFollower(IReadOnlyList All, Missing => Missing, + Default => Default, + AnyCPU => AnyCPU, AnySpaceCPU => AnySpaceCPU, Win32 => Win32, x64 => x64, diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Model/ProjectType.cs b/src/Microsoft.VisualStudio.SolutionPersistence/Model/ProjectType.cs index c4ac27aa..dfbb3d13 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Model/ProjectType.cs +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Model/ProjectType.cs @@ -32,7 +32,7 @@ public sealed class ProjectType(Guid projectTypeId, IReadOnlyList /// - /// If a project type should not build, it should have a single rule with Build set to false. + /// If a project type should not build, it should have a single rule with Build set to . /// public IReadOnlyList ConfigurationRules { get; } = rules; @@ -51,8 +51,8 @@ public sealed class ProjectType(Guid projectTypeId, IReadOnlyList - /// Gets references a base project type to inherit its configuration rules and project type id. - /// This uses the Name or Extension of the base project type to find it. + /// Gets a references to a base project type to inherit its configuration rules and project type id. + /// This uses the or of the base project type to find it. /// public string? BasedOn { get; init; } } diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Model/ProjectTypeTable.BuiltInTypes.cs b/src/Microsoft.VisualStudio.SolutionPersistence/Model/ProjectTypeTable.BuiltInTypes.cs index 4f58937e..fc66fc31 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Model/ProjectTypeTable.BuiltInTypes.cs +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Model/ProjectTypeTable.BuiltInTypes.cs @@ -8,6 +8,8 @@ internal sealed partial class ProjectTypeTable { internal static readonly ConfigurationRule[] NoBuildRules = [ModelHelper.CreateNoBuildRule()]; + internal static readonly ConfigurationRule NoPlatformsRule = ModelHelper.CreateNoPlatformsRule(); + internal static readonly Guid VCXProj = new Guid("8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"); internal static readonly Guid SolutionFolder = new Guid("2150E333-8FDC-42A3-9474-1A3956D46DE8"); @@ -87,6 +89,7 @@ internal sealed partial class ProjectTypeTable new ProjectType(new Guid("00D1A9C2-B5F0-4AF3-8072-F6C62B433612"), NoBuildRules) { Name = "SQL", Extension = ".sqlproj" }, new ProjectType(new Guid("0C603C2C-620A-423B-A800-4F3E2F6281F1"), NoBuildRules) { Name = "U-SQL-DB", Extension = ".usqldbproj" }, new ProjectType(new Guid("182E2583-ECAD-465B-BB50-91101D7C24CE"), NoBuildRules) { Name = "U-SQL", Extension = ".usqlproj" }, + new ProjectType(new Guid("F14B399A-7131-4C87-9E4B-1186C45EF12D"), [NoPlatformsRule]) { Name = "SSRS", Extension = ".rptproj" }, // Azure project types new ProjectType(new Guid("A07B5EB6-E848-4116-A8D0-A826331D98C6"), NoBuildRules) { Name = "Fabric", Extension = ".sfproj" }, diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Model/StringTable.cs b/src/Microsoft.VisualStudio.SolutionPersistence/Model/StringTable.cs index b2aa68cb..bfcdadbe 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Model/StringTable.cs +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Model/StringTable.cs @@ -69,4 +69,10 @@ internal void AddString(string str) { _ = this.GetString(str); } + + // Used to test the string table. + internal bool Contains(string str) + { + return this.strings.Contains(str); + } } diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/SlnV12/SlnV12Extensions.cs b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/SlnV12/SlnV12Extensions.cs index 055f2b5e..fd58f775 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/SlnV12/SlnV12Extensions.cs +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/SlnV12/SlnV12Extensions.cs @@ -253,9 +253,10 @@ static void SetProjectConfigurationPlatforms(SolutionModel solution, SolutionPro project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.BuildType, buildType, platform, BuildTypeNames.Missing)); project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Platform, buildType, platform, PlatformNames.Missing)); - // In the old .sln file the default configuration is not to build unless there is a build line. - // This rule will get overwritten by the build line if it exists. + // In the old .sln file the default configuration is not to build/deploy unless there is a build/deploy line. + // This rule will get overwritten by the build/deploy line if it exists. project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Build, buildType, platform, bool.FalseString)); + project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Deploy, buildType, platform, bool.FalseString)); } } } @@ -336,6 +337,11 @@ void ParseProjectConfigLine(SolutionModel solutionModel, string name, string val projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.BuildType, solutionBuildType, solutionPlatform, projectBuildType)); projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Platform, solutionBuildType, solutionPlatform, projectPlatform)); } + else if (!value.IsNullOrEmpty()) + { + // If the project configuration does not have a platform, just set the build type. + projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.BuildType, solutionBuildType, solutionPlatform, value)); + } break; case ConfigLineType.Build: @@ -454,18 +460,15 @@ not SectionName.ExtensibilityGlobals and continue; } - bool isMissing = mapping.BuildType == BuildTypeNames.Missing || mapping.Platform == PlatformNames.Missing; - // Default project mapping in SLN was to use "Any CPU" - string platform = mapping.Platform; - if (platform == PlatformNames.AnyCPU) - { - platform = PlatformNames.AnySpaceCPU; - } + string platform = + mapping.Platform == PlatformNames.AnyCPU ? PlatformNames.AnySpaceCPU : + mapping.Platform; - string prjCfgPlatString = $"{mapping.BuildType}|{platform}"; + // If just the platform is missing, the project doesn't support platforms and only the build type should be written. + string prjCfgPlatString = platform == PlatformNames.Missing ? mapping.BuildType : $"{mapping.BuildType}|{platform}"; - if (!isMissing) + if (mapping.BuildType != BuildTypeNames.Missing) { WriteProperty(propertyBag, projectId, entry.SlnKey, ActiveCfgSuffix, prjCfgPlatString); } diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/Keywords.cs b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/Keywords.cs index 98ff5040..76d21286 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/Keywords.cs +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/Keywords.cs @@ -37,6 +37,7 @@ internal enum Keyword Extension, BasedOn, IsBuildable, + SupportsPlatform, // Configuration properties Configuration, @@ -87,6 +88,7 @@ static Keywords() new(nameof(Keyword.Extension), Keyword.Extension), new(nameof(Keyword.BasedOn), Keyword.BasedOn), new(nameof(Keyword.IsBuildable), Keyword.IsBuildable), + new(nameof(Keyword.SupportsPlatform), Keyword.SupportsPlatform), new(nameof(Keyword.Configuration), Keyword.Configuration), new(nameof(Keyword.Dimension), Keyword.Dimension), new(nameof(Keyword.BuildType), Keyword.BuildType), @@ -125,6 +127,8 @@ internal static StringTable WithSolutionConstants(this StringTable stringTable) stringTable.AddString(BuildTypeNames.Debug); stringTable.AddString(BuildTypeNames.Release); stringTable.AddString(PlatformNames.All); + stringTable.AddString(PlatformNames.Missing); + stringTable.AddString(PlatformNames.Default); stringTable.AddString(PlatformNames.AnyCPU); stringTable.AddString(PlatformNames.AnySpaceCPU); stringTable.AddString(PlatformNames.Win32); diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/Slnx.xsd b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/Slnx.xsd index e395847f..399bf6ed 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/Slnx.xsd +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/Slnx.xsd @@ -7,10 +7,8 @@ - - - - + + @@ -38,7 +36,8 @@ - + + diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/SlnxSerializerSettings.cs b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/SlnxSerializerSettings.cs index 0b24b591..d8b38890 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/SlnxSerializerSettings.cs +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/SlnxSerializerSettings.cs @@ -15,8 +15,8 @@ public readonly struct SlnxSerializerSettings(SlnxSerializerSettings settings) { /// /// Gets a value indicating whether to keep whitespace when writing the solution file. - /// If this is true, the solution file will be written with the same whitespace as the original file. - /// Default is true. + /// If this is , the solution file will be written with the same whitespace as the original file. + /// Default is . /// public bool? PreserveWhitespace { get; init; } = settings.PreserveWhitespace; diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/SlnxFile.cs b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/SlnxFile.cs index 02a14020..96a7a6c6 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/SlnxFile.cs +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/SlnxFile.cs @@ -86,7 +86,7 @@ internal string ConvertToUserPath(string projectPath) /// Update the Xml DOM with changes from the model. /// /// - /// true if any changes were made to the XML. + /// if any changes were made to the XML. /// internal bool ApplyModel(SolutionModel model) { diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlContainer.ApplyModel.cs b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlContainer.ApplyModel.cs index 34fdccd7..d5543ae2 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlContainer.ApplyModel.cs +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlContainer.ApplyModel.cs @@ -57,7 +57,7 @@ internal List DebugChildNodes /// The list of existing decorator items in the XML. /// The element name for the decorator, can be dynamic by using getDecoratorElementName. /// Applies the model item changes to the decorator. - /// true if the XML was changed. + /// if the XML was changed. internal bool ApplyModelItemsToXml( List<(string ItemRef, TModelItem Item)>? modelItems, ref ItemRefList decoratorItems, diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlContainer.cs b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlContainer.cs index 6de22d84..543d66f9 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlContainer.cs +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlContainer.cs @@ -50,7 +50,7 @@ internal override void UpdateFromXml() /// /// Wraps the given element with a new decorator and adds it to the cache. - /// If this is a new element, pass itemRef and validateItemRef to true. + /// If this is a new element, pass itemRef and validateItemRef to . /// private XmlDecorator? CreateChildDecorator(XmlElement xmlElement, string? itemRef = null, bool validateItemRef = false) { diff --git a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlProjectType.cs b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlProjectType.cs index 30d2154d..5ad61b6b 100644 --- a/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlProjectType.cs +++ b/src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlProjectType.cs @@ -18,36 +18,61 @@ internal sealed class XmlProjectType(SlnxFile root, XmlElement element) : public Keyword ItemRefAttribute => Keyword.TypeId; + /// internal Guid TypeId { get => this.GetXmlAttributeGuid(Keyword.TypeId); set => this.UpdateXmlAttributeGuid(Keyword.TypeId, value); } + /// internal string? Name { get => this.GetXmlAttribute(Keyword.Name); set => this.UpdateXmlAttribute(Keyword.Name, value); } + /// internal string? Extension { get => this.GetXmlAttribute(Keyword.Extension); set => this.UpdateXmlAttribute(Keyword.Extension, value); } + /// internal string? BasedOn { get => this.GetXmlAttribute(Keyword.BasedOn); set => this.UpdateXmlAttribute(Keyword.BasedOn, value); } + /// + /// Gets or sets a value indicating whether the project type is buildable. + /// + /// + /// Default is . + /// When automatically sets configuration rules to never build. + /// internal bool IsBuildable { get => this.GetXmlAttributeBool(Keyword.IsBuildable, defaultValue: true); set => this.UpdateXmlAttributeBool(Keyword.IsBuildable, value, defaultValue: true); } + /// + /// Gets or sets a value indicating whether the project type supports platform configurations. + /// + /// + /// Default is . + /// When automatically adds configuration rule to remove platform mappings. + /// This setting is ignored if is . + /// + internal bool SupportsPlatform + { + get => this.GetXmlAttributeBool(Keyword.SupportsPlatform, defaultValue: true); + set => this.UpdateXmlAttributeBool(Keyword.SupportsPlatform, value, defaultValue: true); + } + private protected override bool AllowEmptyItemRef => true; /// @@ -124,7 +149,10 @@ internal override bool IsValid() internal ProjectType ToModel() { - ConfigurationRule[] rules = this.IsBuildable ? [.. this.configurationRules.ToModel()] : ProjectTypeTable.NoBuildRules; + ConfigurationRule[] rules = + !this.IsBuildable ? ProjectTypeTable.NoBuildRules : + !this.SupportsPlatform ? [ProjectTypeTable.NoPlatformsRule, .. this.configurationRules.ToModel()] : + /*default*/ [.. this.configurationRules.ToModel()]; return new ProjectType(this.TypeId, rules) { @@ -164,6 +192,7 @@ internal bool ApplyModelToXml(ProjectType modelProjectType) ConfigurationRuleFollower rules = new ConfigurationRuleFollower(modelProjectType.ConfigurationRules); bool isBuildable = rules.GetIsBuildable() ?? true; + bool supportsPlatform = rules.GetProjectPlatform() != PlatformNames.Missing; if (this.IsBuildable != isBuildable) { @@ -171,7 +200,26 @@ internal bool ApplyModelToXml(ProjectType modelProjectType) modified = true; } - modified |= this.configurationRules.ApplyModelToXml(this, isBuildable ? modelProjectType.ConfigurationRules : []); + if (this.SupportsPlatform != supportsPlatform) + { + this.SupportsPlatform = supportsPlatform; + modified = true; + } + + // Determine which rules to serizlize. Remove rules implied by IsBuildable and SupportsPlatform. + IReadOnlyList? rulesToApply = + !isBuildable ? [] : + !supportsPlatform ? RemovePlatformRules(modelProjectType.ConfigurationRules) : + modelProjectType.ConfigurationRules; + + modified |= this.configurationRules.ApplyModelToXml(this, rulesToApply); return modified; + + // Remove any platform rules from the list. + static List RemovePlatformRules(IReadOnlyList rules) => + rules.WhereToList( + predicate: static (rule, _) => rule.Dimension != BuildDimension.Platform, + selector: static (rule, _) => rule, + (object?)null); } } diff --git a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripClassicSln.cs b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripClassicSln.cs index 5c495e30..5676fd5f 100644 --- a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripClassicSln.cs +++ b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripClassicSln.cs @@ -42,6 +42,9 @@ public class RoundTripClassicSln [Fact] public Task FolderIdAsync() => TestRoundTripSerializerAsync(SlnAssets.LoadResource("FolderId.sln")); + [Fact] + public Task ReportProjectAsync() => TestRoundTripSerializerAsync(SlnAssets.LoadResource("Report Project.sln")); + [Theory] [MemberData(nameof(ClassicSlnFiles))] public Task AllClassicSolutionAsync(ResourceName sampleFile) diff --git a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripClassicSlnThruSlnxStream.cs b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripClassicSlnThruSlnxStream.cs index 4580cd11..bcc0143f 100644 --- a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripClassicSlnThruSlnxStream.cs +++ b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripClassicSlnThruSlnxStream.cs @@ -36,6 +36,9 @@ public class RoundTripClassicSlnThruSlnxStream [Fact] public Task MissingConfigurationsThruSlnxStreamAsync() => TestRoundTripSerializerAsync(SlnAssets.ClassicSlnMissingConfigurations, SlnAssets.XmlSlnxMissingConfigurations); + [Fact] + public Task ReportProjectThruSlnxStreamAsync() => TestRoundTripSerializerAsync(SlnAssets.LoadResource("Report Project.sln"), SlnAssets.LoadResource("Report Project.slnx")); + /// /// Round trip a .SLN file through the slnx serializer. /// diff --git a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripXmlSlnx.cs b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripXmlSlnx.cs index 6a588175..8ffc0e62 100644 --- a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripXmlSlnx.cs +++ b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripXmlSlnx.cs @@ -51,6 +51,9 @@ public class RoundTripXmlSlnx [Fact] public Task VersionMinAsync() => TestRoundTripSerializerAsync(SlnAssets.XmlSlnxVersionMin); + [Fact] + public Task ReportProject() => TestRoundTripSerializerAsync(SlnAssets.LoadResource("Report Project.slnx")); + [Theory] [MemberData(nameof(XmlSlnxFiles))] public Task AllXmlSolutionAsync(ResourceName sampleFile) diff --git a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripXmlSlnxThruModelCopy.cs b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripXmlSlnxThruModelCopy.cs index 1f79e3e3..2aa1d02e 100644 --- a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripXmlSlnxThruModelCopy.cs +++ b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/RoundTripXmlSlnxThruModelCopy.cs @@ -49,6 +49,9 @@ public Task CommentsAsync() [Fact] public Task TraditionalAsync() => TestRoundTripSerializerAsync(SlnAssets.XmlSlnxTraditional); + [Fact] + public Task ReportProjectAsync() => TestRoundTripSerializerAsync(SlnAssets.LoadResource("Report Project.slnx")); + private static async Task TestRoundTripSerializerAsync(ResourceStream slnStream) { // Open the Model from stream. diff --git a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/StringTests.cs b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/StringTests.cs index eed39ab4..124a036d 100644 --- a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/StringTests.cs +++ b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/StringTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Reflection; using Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; namespace Serialization; @@ -10,6 +11,8 @@ namespace Serialization; /// public sealed class StringTests { + private readonly StringTable stringTable = new StringTable().WithSolutionConstants(); + /// /// Make sure all keywords have a string representation. /// @@ -19,11 +22,40 @@ public void KeywordStrings() // Validate that if any new keywords are added to the enum, they are in the mapping tables. foreach (Keyword keyword in Enum.GetValues(typeof(Keyword))) { - if (keyword is not Keyword.Unknown and not < 0 and not >= Keyword.MaxProp && - keyword.ToXmlString() is null) + if (keyword is not Keyword.Unknown and not < 0 and not >= Keyword.MaxProp) { - Assert.Fail($"Keyword {keyword} does not have a string representation."); + string keywordStr = keyword.ToXmlString(); + Assert.True(keywordStr is not null, $"Keyword {keyword} does not have a string representation."); + Assert.True(this.stringTable.Contains(keywordStr), $"StringTable missing {keyword.ToXmlString()}"); } } } + + /// + /// Make sure all build type names have a string representation. + /// + [Fact] + public void BuildTypeNamesTest() + { + foreach (FieldInfo fieldInfo in typeof(BuildTypeNames).GetFields(BindingFlags.NonPublic | BindingFlags.Static)) + { + string value = fieldInfo.GetValue(null) as string ?? string.Empty; + Assert.True(BuildTypeNames.TryGetKnown(value.AsSpan(), out string? str), $"BuildType lookup missing {value}"); + Assert.True(this.stringTable.Contains(str), $"StringTable missing {str}"); + } + } + + /// + /// Make sure all platform names have a string representation. + /// + [Fact] + public void PlatformNamesTest() + { + foreach (FieldInfo fieldInfo in typeof(PlatformNames).GetFields(BindingFlags.NonPublic | BindingFlags.Static)) + { + string value = fieldInfo.GetValue(null) as string ?? string.Empty; + Assert.True(PlatformNames.TryGetKnown(value.AsSpan(), out string? str), $"Platform lookup missing {value}"); + Assert.True(this.stringTable.Contains(str), $"StringTable missing {str}"); + } + } } diff --git a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Configurations/BuiltInProjectTypes.slnx.xml b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Configurations/BuiltInProjectTypes.slnx.xml index 88022ce4..eb00b37f 100644 --- a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Configurations/BuiltInProjectTypes.slnx.xml +++ b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Configurations/BuiltInProjectTypes.slnx.xml @@ -32,6 +32,7 @@ + diff --git a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Configurations/MissingConfigurations.sln.txt b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Configurations/MissingConfigurations.sln.txt index 7cd290b1..c6e986fb 100644 --- a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Configurations/MissingConfigurations.sln.txt +++ b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Configurations/MissingConfigurations.sln.txt @@ -25,7 +25,7 @@ Global {B0C8586B-AD06-484D-A425-78449EEB0AFA}.Release|x64.Build.0 = Release|x64 {B0C8586B-AD06-484D-A425-78449EEB0AFA}.Release|x86.ActiveCfg = Release|Win32 {B0C8586B-AD06-484D-A425-78449EEB0AFA}.Release|x86.Build.0 = Release|Win32 - {D53DB013-AEE1-48D8-874F-509000819E30}.Debug|x86.Build.0 = ?|? + {D53DB013-AEE1-48D8-874F-509000819E30}.Debug|x86.Build.0 = ? {D53DB013-AEE1-48D8-874F-509000819E30}.Release|x64.ActiveCfg = Release|x64 {D53DB013-AEE1-48D8-874F-509000819E30}.Release|x64.Build.0 = Release|x64 EndGlobalSection diff --git a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Report Project.sln.txt b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Report Project.sln.txt new file mode 100644 index 00000000..d3281505 --- /dev/null +++ b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Report Project.sln.txt @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.35821.254 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{F14B399A-7131-4C87-9E4B-1186C45EF12D}") = "Report Project", "Report Project.rptproj", "{5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Default = Debug|Default + DebugLocal|Default = DebugLocal|Default + Release|Default = Release|Default + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Debug|Default.ActiveCfg = Debug + {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Debug|Default.Build.0 = Debug + {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Debug|Default.Deploy.0 = Debug + {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.DebugLocal|Default.ActiveCfg = DebugLocal + {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.DebugLocal|Default.Build.0 = DebugLocal + {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Release|Default.ActiveCfg = Release + {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Release|Default.Build.0 = Release + {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Release|Default.Deploy.0 = Release + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AB744758-6EDA-4264-84B6-7396FCA3E003} + EndGlobalSection +EndGlobal diff --git a/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Report Project.slnx.xml b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Report Project.slnx.xml new file mode 100644 index 00000000..25e0f20a --- /dev/null +++ b/test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Report Project.slnx.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + +