From 95d1add2df97f38c4dab81aca19ba498aaba56dc Mon Sep 17 00:00:00 2001 From: Billy Richardson Date: Thu, 12 Sep 2024 13:15:01 -0700 Subject: [PATCH 1/2] Add code analysis --- src/.editorconfig | 7 +++++++ src/AggregateConfigBuildTask.sln | 1 + src/Contracts/Contracts.csproj | 1 - src/Directory.Build.props | 10 ++++++++++ src/Directory.Packages.props | 2 ++ src/Task/AggregateConfigBuildTask.csproj | 2 -- src/UnitTests/UnitTests.csproj | 1 - 7 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 src/Directory.Build.props diff --git a/src/.editorconfig b/src/.editorconfig index 1f62320..bcabf23 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -2,3 +2,10 @@ # CS1591: Missing XML comment for publicly visible type or member dotnet_diagnostic.CS1591.severity = suggestion + +# Organize usings +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = true + +# Missing usings should be reported as error (IDE0005) +dotnet_diagnostic.IDE0005.severity = error diff --git a/src/AggregateConfigBuildTask.sln b/src/AggregateConfigBuildTask.sln index 17957ab..d4bf6f8 100644 --- a/src/AggregateConfigBuildTask.sln +++ b/src/AggregateConfigBuildTask.sln @@ -12,6 +12,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{29D0AE56-C184-4741-824F-521198552928}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + Directory.Build.props = Directory.Build.props Directory.Packages.props = Directory.Packages.props EndProjectSection EndProject diff --git a/src/Contracts/Contracts.csproj b/src/Contracts/Contracts.csproj index 56a26c6..261ad5b 100644 --- a/src/Contracts/Contracts.csproj +++ b/src/Contracts/Contracts.csproj @@ -2,7 +2,6 @@ netstandard2.0 - true diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..41dcda1 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,10 @@ + + + true + true + true + latest + false + AllEnabledByDefault + + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 6233193..ff1e112 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -25,6 +25,8 @@ + + diff --git a/src/Task/AggregateConfigBuildTask.csproj b/src/Task/AggregateConfigBuildTask.csproj index af74e8f..2022b7b 100644 --- a/src/Task/AggregateConfigBuildTask.csproj +++ b/src/Task/AggregateConfigBuildTask.csproj @@ -2,9 +2,7 @@ netstandard2.0 - true true - true true true true diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 9b933be..365f37c 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -5,7 +5,6 @@ net8.0 false disable - true false true CS1591 From 6f821f75d8d4d88854ed0748b3721e14064eb9d1 Mon Sep 17 00:00:00 2001 From: Billy Richardson Date: Thu, 12 Sep 2024 16:26:56 -0700 Subject: [PATCH 2/2] Fix ca errors --- src/Contracts/Contracts.csproj | 1 + src/Contracts/InputOutputEnums.cs | 16 ++--- src/Directory.Build.props | 19 ++--- src/Task/AggregateConfig.cs | 16 ++--- src/Task/AggregateConfigBuildTask.csproj | 2 +- .../FileHandlers/ArmParametersFileHandler.cs | 18 ++--- src/Task/FileHandlers/FileHandlerFactory.cs | 24 +++---- src/Task/FileHandlers/JsonFileHandler.cs | 3 +- src/Task/FileSystem/FileSystem.cs | 2 +- src/Task/JsonHelper.cs | 7 +- src/Task/ObjectManager.cs | 36 +++++----- src/UnitTests/TaskTestBase.cs | 71 ++++++++++--------- src/UnitTests/UnitTests.csproj | 2 +- src/UnitTests/VirtualFileSystem.cs | 22 ++++-- 14 files changed, 127 insertions(+), 112 deletions(-) diff --git a/src/Contracts/Contracts.csproj b/src/Contracts/Contracts.csproj index 261ad5b..077033a 100644 --- a/src/Contracts/Contracts.csproj +++ b/src/Contracts/Contracts.csproj @@ -2,6 +2,7 @@ netstandard2.0 + CA1027 diff --git a/src/Contracts/InputOutputEnums.cs b/src/Contracts/InputOutputEnums.cs index e81f362..f5d8fd3 100644 --- a/src/Contracts/InputOutputEnums.cs +++ b/src/Contracts/InputOutputEnums.cs @@ -1,20 +1,20 @@ namespace AggregateConfigBuildTask.Contracts { - public enum OutputTypeEnum + public enum OutputType { - Json, - Arm, + Json = 0, + Arm = 1, ArmParameter = Arm, - Yml, + Yml = 2, Yaml = Yml } - public enum InputTypeEnum + public enum InputType { - Json, - Arm, + Json = 0, + Arm = 1, ArmParameter = Arm, - Yml, + Yml = 2, Yaml = Yml } } diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 41dcda1..5766305 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,10 +1,13 @@ - - true - true - true - latest - false - AllEnabledByDefault - + + + true + true + true + latest + false + false + AllEnabledByDefault + + diff --git a/src/Task/AggregateConfig.cs b/src/Task/AggregateConfig.cs index bc9aaf0..f073261 100644 --- a/src/Task/AggregateConfig.cs +++ b/src/Task/AggregateConfig.cs @@ -26,7 +26,7 @@ public class AggregateConfig : Task [Required] public string OutputType { get; set; } - public bool AddSourceProperty { get; set; } = false; + public bool AddSourceProperty { get; set; } public string[] AdditionalProperties { get; set; } @@ -48,18 +48,18 @@ public override bool Execute() OutputFile = Path.GetFullPath(OutputFile); - if (!Enum.TryParse(OutputType, true, out OutputTypeEnum outputType) || - !Enum.IsDefined(typeof(OutputTypeEnum), outputType)) + if (!Enum.TryParse(OutputType, true, out OutputType outputType) || + !Enum.IsDefined(typeof(OutputType), outputType)) { - Log.LogError("Invalid OutputType: {0}. Available options: {1}", OutputType, string.Join(", ", Enum.GetNames(typeof(OutputTypeEnum)))); + Log.LogError("Invalid OutputType: {0}. Available options: {1}", OutputType, string.Join(", ", Enum.GetNames(typeof(OutputType)))); return false; } - InputTypeEnum inputType = InputTypeEnum.Yaml; + InputType inputType = Contracts.InputType.Yaml; if (!string.IsNullOrEmpty(InputType) && - (!Enum.TryParse(InputType, true, out inputType) || !Enum.IsDefined(typeof(InputTypeEnum), inputType))) + (!Enum.TryParse(InputType, true, out inputType) || !Enum.IsDefined(typeof(InputType), inputType))) { - Log.LogError("Invalid InputType: {0}. Available options: {1}", InputType, string.Join(", ", Enum.GetNames(typeof(InputTypeEnum)))); + Log.LogError("Invalid InputType: {0}. Available options: {1}", InputType, string.Join(", ", Enum.GetNames(typeof(InputType)))); return false; } @@ -103,7 +103,7 @@ private void EmitHeader() .GetCustomAttribute()? .InformationalVersion; - Log.LogMessage($"AggregateConfig Version: {informationalVersion}"); + Log.LogMessage(MessageImportance.High, $"AggregateConfig Version: {informationalVersion}"); } } } diff --git a/src/Task/AggregateConfigBuildTask.csproj b/src/Task/AggregateConfigBuildTask.csproj index 2022b7b..a1ed647 100644 --- a/src/Task/AggregateConfigBuildTask.csproj +++ b/src/Task/AggregateConfigBuildTask.csproj @@ -6,7 +6,7 @@ true true true - NU5100 + NU5100,CA1031,CA1819 diff --git a/src/Task/FileHandlers/ArmParametersFileHandler.cs b/src/Task/FileHandlers/ArmParametersFileHandler.cs index 51ef876..7a659b2 100644 --- a/src/Task/FileHandlers/ArmParametersFileHandler.cs +++ b/src/Task/FileHandlers/ArmParametersFileHandler.cs @@ -8,7 +8,9 @@ namespace AggregateConfigBuildTask.FileHandlers { public class ArmParametersFileHandler : IOutputWriter, IInputReader { - readonly IFileSystem fileSystem; + private readonly IFileSystem fileSystem; + + private readonly JsonSerializerOptions jsonOptions = new JsonSerializerOptions { WriteIndented = true }; internal ArmParametersFileHandler(IFileSystem fileSystem) { @@ -16,11 +18,11 @@ internal ArmParametersFileHandler(IFileSystem fileSystem) } /// - public ValueTask ReadInput(string inputPath) + public async ValueTask ReadInput(string inputPath) { using (var stream = fileSystem.OpenRead(inputPath)) { - using (var jsonDoc = JsonDocument.Parse(stream)) + using (var jsonDoc = await JsonDocument.ParseAsync(stream).ConfigureAwait(false)) { if (jsonDoc.RootElement.TryGetProperty("parameters", out JsonElement parameters)) { @@ -41,10 +43,10 @@ public ValueTask ReadInput(string inputPath) } var modifiedJson = modifiedParameters.ToJsonString(); - return new ValueTask(Task.FromResult(JsonSerializer.Deserialize(modifiedJson))); + return JsonSerializer.Deserialize(modifiedJson); } - return new ValueTask(Task.FromResult(jsonDoc.RootElement.Clone())); + return jsonDoc.RootElement.Clone(); } } } @@ -74,8 +76,6 @@ public void WriteOutput(JsonElement? mergedData, string outputPath) ["contentVersion"] = "1.0.0.0", ["parameters"] = parameters }; - - var jsonOptions = new JsonSerializerOptions { WriteIndented = true }; var jsonContent = JsonSerializer.Serialize(armTemplate, jsonOptions); fileSystem.WriteAllText(outputPath, jsonContent); } @@ -90,7 +90,7 @@ public void WriteOutput(JsonElement? mergedData, string outputPath) /// /// The JsonElement value to evaluate. /// A string representing the ARM template parameter type. - private string GetParameterType(JsonElement value) + private static string GetParameterType(JsonElement value) { switch (value.ValueKind) { @@ -115,7 +115,7 @@ private string GetParameterType(JsonElement value) } } - private JsonNode ConvertElementToNode(JsonElement element) + private static JsonNode ConvertElementToNode(JsonElement element) { // Use GetRawText to get the JSON string representation of the JsonElement var jsonString = element.GetRawText(); diff --git a/src/Task/FileHandlers/FileHandlerFactory.cs b/src/Task/FileHandlers/FileHandlerFactory.cs index bc13876..a7f9c0d 100644 --- a/src/Task/FileHandlers/FileHandlerFactory.cs +++ b/src/Task/FileHandlers/FileHandlerFactory.cs @@ -6,45 +6,45 @@ namespace AggregateConfigBuildTask.FileHandlers { public static class FileHandlerFactory { - internal static IOutputWriter GetOutputWriter(IFileSystem fileSystem, OutputTypeEnum format) + internal static IOutputWriter GetOutputWriter(IFileSystem fileSystem, OutputType format) { switch (format) { - case OutputTypeEnum.Json: + case OutputType.Json: return new JsonFileHandler(fileSystem); - case OutputTypeEnum.Yaml: + case OutputType.Yaml: return new YamlFileHandler(fileSystem); - case OutputTypeEnum.Arm: + case OutputType.Arm: return new ArmParametersFileHandler(fileSystem); default: throw new ArgumentException("Unsupported format"); } } - internal static IInputReader GetInputReader(IFileSystem fileSystem, InputTypeEnum format) + internal static IInputReader GetInputReader(IFileSystem fileSystem, InputType format) { switch (format) { - case InputTypeEnum.Yaml: + case InputType.Yaml: return new YamlFileHandler(fileSystem); - case InputTypeEnum.Json: + case InputType.Json: return new JsonFileHandler(fileSystem); - case InputTypeEnum.Arm: + case InputType.Arm: return new ArmParametersFileHandler(fileSystem); default: throw new ArgumentException("Unsupported input format"); } } - internal static List GetExpectedFileExtensions(InputTypeEnum inputType) + internal static List GetExpectedFileExtensions(InputType inputType) { switch (inputType) { - case InputTypeEnum.Json: + case InputType.Json: return new List { ".json" }; - case InputTypeEnum.Yaml: + case InputType.Yaml: return new List { ".yml", ".yaml" }; - case InputTypeEnum.Arm: + case InputType.Arm: return new List { ".json" }; default: throw new ArgumentException("Unsupported input type"); diff --git a/src/Task/FileHandlers/JsonFileHandler.cs b/src/Task/FileHandlers/JsonFileHandler.cs index 5f67e7a..b64e343 100644 --- a/src/Task/FileHandlers/JsonFileHandler.cs +++ b/src/Task/FileHandlers/JsonFileHandler.cs @@ -7,6 +7,8 @@ public class JsonFileHandler : IOutputWriter, IInputReader { readonly IFileSystem fileSystem; + private readonly JsonSerializerOptions jsonOptions = new JsonSerializerOptions { WriteIndented = true }; + internal JsonFileHandler(IFileSystem fileSystem) { this.fileSystem = fileSystem; @@ -24,7 +26,6 @@ public ValueTask ReadInput(string inputPath) /// public void WriteOutput(JsonElement? mergedData, string outputPath) { - var jsonOptions = new JsonSerializerOptions { WriteIndented = true }; var jsonContent = JsonSerializer.Serialize(mergedData, jsonOptions); fileSystem.WriteAllText(outputPath, jsonContent); } diff --git a/src/Task/FileSystem/FileSystem.cs b/src/Task/FileSystem/FileSystem.cs index 70914bc..f3d3c8a 100644 --- a/src/Task/FileSystem/FileSystem.cs +++ b/src/Task/FileSystem/FileSystem.cs @@ -2,7 +2,7 @@ namespace AggregateConfigBuildTask { - internal class FileSystem : IFileSystem + internal sealed class FileSystem : IFileSystem { /// public string[] GetFiles(string path, string searchPattern) diff --git a/src/Task/JsonHelper.cs b/src/Task/JsonHelper.cs index 46a1073..b48ebb3 100644 --- a/src/Task/JsonHelper.cs +++ b/src/Task/JsonHelper.cs @@ -72,6 +72,7 @@ public static Dictionary ParseAdditionalProperties(string[] prop { var additionalPropertiesDict = new Dictionary(); const string unicodeEscape = "\u001F"; + char[] split = new[] { '=' }; if (properties != null) { @@ -79,7 +80,7 @@ public static Dictionary ParseAdditionalProperties(string[] prop { var sanitizedProperty = property.Replace(@"\=", unicodeEscape); - var keyValue = sanitizedProperty.Split(new[] { '=' }, 2); + var keyValue = sanitizedProperty.Split(split, 2); if (keyValue.Length == 2) { @@ -104,10 +105,10 @@ public static async Task ConvertObjectToJsonElement(object value) using (var memoryStream = new MemoryStream()) { - await JsonSerializer.SerializeAsync(memoryStream, value); + await JsonSerializer.SerializeAsync(memoryStream, value).ConfigureAwait(false); memoryStream.Seek(0, SeekOrigin.Begin); - using (var jsonDocument = await JsonDocument.ParseAsync(memoryStream)) + using (var jsonDocument = await JsonDocument.ParseAsync(memoryStream).ConfigureAwait(false)) { return jsonDocument.RootElement.Clone(); } diff --git a/src/Task/ObjectManager.cs b/src/Task/ObjectManager.cs index 8a5ad02..1c1d91d 100644 --- a/src/Task/ObjectManager.cs +++ b/src/Task/ObjectManager.cs @@ -14,7 +14,7 @@ namespace AggregateConfigBuildTask { internal static class ObjectManager { - public static async Task MergeFileObjects(string fileObjectDirectoryPath, InputTypeEnum inputType, bool addSourceProperty, IFileSystem fileSystem, TaskLoggingHelper log) + public static async Task MergeFileObjects(string fileObjectDirectoryPath, InputType inputType, bool addSourceProperty, IFileSystem fileSystem, TaskLoggingHelper log) { var finalResults = new ConcurrentBag(); JsonElement? finalResult = null; @@ -22,7 +22,7 @@ internal static class ObjectManager var expectedExtensions = FileHandlerFactory.GetExpectedFileExtensions(inputType); var fileGroups = fileSystem.GetFiles(fileObjectDirectoryPath, "*.*") - .Where(file => expectedExtensions.Contains(Path.GetExtension(file).ToLower())) + .Where(file => expectedExtensions.Contains(Path.GetExtension(file), StringComparer.OrdinalIgnoreCase)) .ToList() .Chunk(100); @@ -48,7 +48,7 @@ await fileGroups.ForEachAsync(Environment.ProcessorCount, JsonElement fileData; try { - fileData = await outputWriter.ReadInput(file); + fileData = await outputWriter.ReadInput(file).ConfigureAwait(false); } catch (Exception ex) { @@ -59,9 +59,9 @@ await fileGroups.ForEachAsync(Environment.ProcessorCount, } // Merge the deserialized object into the final result - finalResults.Add(await ObjectManager.MergeObjects(intermediateResult, fileData, file, addSourceProperty)); + finalResults.Add(await MergeObjects(intermediateResult, fileData, file, addSourceProperty).ConfigureAwait(false)); } - }); + }).ConfigureAwait(false); if (hasError) { @@ -70,7 +70,7 @@ await fileGroups.ForEachAsync(Environment.ProcessorCount, foreach (var result in finalResults) { - finalResult = await ObjectManager.MergeObjects(finalResult, result, null, false); + finalResult = await MergeObjects(finalResult, result, null, false).ConfigureAwait(false); } return finalResult; @@ -106,14 +106,14 @@ await fileGroups.ForEachAsync(Environment.ProcessorCount, nestedDict["source"] = JsonDocument.Parse($"\"{Path.GetFileNameWithoutExtension(source2)}\"").RootElement; // Update the list at the correct index - obj2NestedList[index] = await JsonHelper.ConvertToJsonElement(nestedDict); + obj2NestedList[index] = await JsonHelper.ConvertToJsonElement(nestedDict).ConfigureAwait(false); } } - jsonObject[key] = await JsonHelper.ConvertToJsonElement(obj2NestedList); + jsonObject[key] = await JsonHelper.ConvertToJsonElement(obj2NestedList).ConfigureAwait(false); } } - obj2 = await JsonHelper.ConvertObjectToJsonElement(jsonObject); + obj2 = await JsonHelper.ConvertObjectToJsonElement(jsonObject).ConfigureAwait(false); } return obj2; @@ -124,11 +124,11 @@ await fileGroups.ForEachAsync(Environment.ProcessorCount, /// public static async Task MergeObjects(JsonElement? obj1, JsonElement? obj2, string source2, bool injectSourceProperty) { - obj1 = await InjectSourceProperty(obj1, source2, injectSourceProperty); - obj2 = await InjectSourceProperty(obj2, source2, injectSourceProperty); + obj1 = await InjectSourceProperty(obj1, source2, injectSourceProperty).ConfigureAwait(false); + obj2 = await InjectSourceProperty(obj2, source2, injectSourceProperty).ConfigureAwait(false); if (obj1 == null) return obj2 ?? default; - if (obj2 == null) return obj1 ?? default; + if (obj2 == null) return obj1.Value; // Handle merging of objects if (obj1.Value.ValueKind == JsonValueKind.Object && obj2.Value.ValueKind == JsonValueKind.Object) @@ -138,9 +138,9 @@ public static async Task MergeObjects(JsonElement? obj1, JsonElemen foreach (var key in dict2.Keys) { - if (dict1.ContainsKey(key)) + if (dict1.TryGetValue(key, out JsonElement dict1Value)) { - dict1[key] = await MergeObjects(dict1[key], dict2[key], source2, injectSourceProperty); + dict1[key] = await MergeObjects(dict1Value, dict2[key], source2, injectSourceProperty).ConfigureAwait(false); } else { @@ -148,7 +148,7 @@ public static async Task MergeObjects(JsonElement? obj1, JsonElemen } } - return await JsonHelper.ConvertToJsonElement(dict1); + return await JsonHelper.ConvertToJsonElement(dict1).ConfigureAwait(false); } // Handle merging of arrays else if (obj1.Value.ValueKind == JsonValueKind.Array && obj2.Value.ValueKind == JsonValueKind.Array) @@ -161,7 +161,7 @@ public static async Task MergeObjects(JsonElement? obj1, JsonElemen list1.Add(item); } - return await JsonHelper.ConvertToJsonElement(list1); + return await JsonHelper.ConvertToJsonElement(list1).ConfigureAwait(false); } // For scalar values, obj2 overwrites obj1 else @@ -188,10 +188,10 @@ public static async Task MergeObjects(JsonElement? obj1, JsonElemen // Add the properties from additionalPropertiesDictionary, converting values to JsonElement foreach (var property in additionalPropertiesDictionary) { - jsonDictionary[property.Key] = await JsonHelper.ConvertObjectToJsonElement(property.Value); + jsonDictionary[property.Key] = await JsonHelper.ConvertObjectToJsonElement(property.Value).ConfigureAwait(false); } - return await JsonHelper.ConvertToJsonElement(jsonDictionary); + return await JsonHelper.ConvertToJsonElement(jsonDictionary).ConfigureAwait(false); } else { diff --git a/src/UnitTests/TaskTestBase.cs b/src/UnitTests/TaskTestBase.cs index d604c17..c1354b5 100644 --- a/src/UnitTests/TaskTestBase.cs +++ b/src/UnitTests/TaskTestBase.cs @@ -1,4 +1,4 @@ -using AggregateConfigBuildTask.Contracts; +using AggregateConfigBuildTask.Contracts; using Microsoft.Build.Framework; using Moq; using Newtonsoft.Json; @@ -14,6 +14,7 @@ public class TaskTestBase { private string testPath; internal IFileSystem mockFileSystem; + private StringComparison comparison = StringComparison.OrdinalIgnoreCase; public void TestInitialize(bool isWindowsMode, string testPath) { @@ -40,7 +41,7 @@ public void ShouldGenerateJsonOutput() { InputDirectory = testPath, OutputFile = testPath + @"\output.json", - OutputType = OutputTypeEnum.Json.ToString(), + OutputType = OutputType.Json.ToString(), AddSourceProperty = true, BuildEngine = Mock.Of() }; @@ -75,7 +76,7 @@ public void ShouldGenerateArmParameterOutput() { InputDirectory = testPath, OutputFile = testPath + @"\output.parameters.json", - OutputType = OutputTypeEnum.Arm.ToString(), + OutputType = OutputType.Arm.ToString(), AddSourceProperty = true, BuildEngine = Mock.Of() }; @@ -90,8 +91,8 @@ public void ShouldGenerateArmParameterOutput() Assert.IsTrue(armTemplate.ContainsKey("parameters")); JObject parameters = (JObject)armTemplate["parameters"]; - Assert.IsNotNull(parameters.GetValue("options")); - Assert.AreEqual("array", parameters.GetValue("options")["type"].ToString()); + Assert.IsNotNull(parameters.GetValue("options", comparison)); + Assert.AreEqual("array", parameters.GetValue("options", comparison)["type"].ToString()); } [TestMethod] @@ -109,7 +110,7 @@ public void ShouldAddSourceProperty() { InputDirectory = testPath, OutputFile = testPath + @"\output.json", - OutputType = OutputTypeEnum.Json.ToString(), + OutputType = OutputType.Json.ToString(), AddSourceProperty = true, BuildEngine = Mock.Of() }; @@ -153,7 +154,7 @@ public void ShouldAddSourcePropertyMultipleFiles() { InputDirectory = testPath, OutputFile = testPath + @"\output.json", - OutputType = OutputTypeEnum.Json.ToString(), + OutputType = OutputType.Json.ToString(), AddSourceProperty = true, BuildEngine = Mock.Of() }; @@ -185,7 +186,7 @@ public void ShouldIncludeAdditionalPropertiesInJson() { InputDirectory = testPath, OutputFile = testPath + @"\output.json", - OutputType = OutputTypeEnum.Json.ToString(), + OutputType = OutputType.Json.ToString(), AddSourceProperty = true, AdditionalProperties = new Dictionary { @@ -220,7 +221,7 @@ public void ShouldIncludeAdditionalPropertiesInArmParameters() { InputDirectory = testPath, OutputFile = testPath + @"\output.json", - OutputType = OutputTypeEnum.Arm.ToString(), + OutputType = OutputType.Arm.ToString(), AddSourceProperty = true, AdditionalProperties = new Dictionary { @@ -238,9 +239,9 @@ public void ShouldIncludeAdditionalPropertiesInArmParameters() string output = mockFileSystem.ReadAllText($"{testPath}\\output.json"); var armTemplate = JsonConvert.DeserializeObject>(output); JObject parameters = (JObject)armTemplate["parameters"]; - Assert.AreEqual("array", parameters.GetValue("options")["type"].ToString()); - Assert.AreEqual("TestRG", parameters.GetValue("Group")["value"].Value()); - Assert.AreEqual("Prod", parameters.GetValue("Environment")["value"].Value()); + Assert.AreEqual("array", parameters.GetValue("options", comparison)["type"].ToString()); + Assert.AreEqual("TestRG", parameters.GetValue("Group", comparison)["value"].Value()); + Assert.AreEqual("Prod", parameters.GetValue("Environment", comparison)["value"].Value()); } [TestMethod] @@ -252,7 +253,7 @@ public void ShouldHandleEmptyDirectory() { InputDirectory = testPath, OutputFile = testPath + @"\output.json", - OutputType = OutputTypeEnum.Json.ToString(), + OutputType = OutputType.Json.ToString(), BuildEngine = Mock.Of() }; @@ -279,7 +280,7 @@ public void ShouldHandleInvalidYamlFormat() { InputDirectory = testPath, OutputFile = testPath + @"\output.json", - OutputType = OutputTypeEnum.Json.ToString(), + OutputType = OutputType.Json.ToString(), BuildEngine = Mock.Of() }; @@ -305,7 +306,7 @@ public void ShouldCorrectlyParseBooleanValues() { InputDirectory = testPath, OutputFile = testPath + @"\output.json", - OutputType = OutputTypeEnum.Arm.ToString(), + OutputType = OutputType.Arm.ToString(), BuildEngine = Mock.Of() }; @@ -317,9 +318,9 @@ public void ShouldCorrectlyParseBooleanValues() string output = mockFileSystem.ReadAllText($"{testPath}\\output.json"); var armTemplate = JsonConvert.DeserializeObject>(output); JObject parameters = (JObject)armTemplate["parameters"]; - Assert.AreEqual("array", parameters.GetValue("options")["type"].ToString()); - Assert.AreEqual("Boolean", parameters.GetValue("options")["value"].First()["isEnabled"].Type.ToString()); - Assert.AreEqual(true, parameters.GetValue("options")["value"].First()["isEnabled"].Value()); + Assert.AreEqual("array", parameters.GetValue("options", comparison)["type"].ToString()); + Assert.AreEqual("Boolean", parameters.GetValue("options", comparison)["value"].First()["isEnabled"].Type.ToString()); + Assert.AreEqual(true, parameters.GetValue("options", comparison)["value"].First()["isEnabled"].Value()); } [TestMethod] @@ -340,10 +341,10 @@ public void ShouldIncludeAdditionalPropertiesInJsonInput() var task = new AggregateConfig(mockFileSystem) { - InputType = InputTypeEnum.Json.ToString(), + InputType = InputType.Json.ToString(), InputDirectory = testPath, OutputFile = testPath + @"\output.json", - OutputType = OutputTypeEnum.Arm.ToString(), + OutputType = OutputType.Arm.ToString(), AddSourceProperty = true, AdditionalProperties = new Dictionary { @@ -361,12 +362,12 @@ public void ShouldIncludeAdditionalPropertiesInJsonInput() string output = mockFileSystem.ReadAllText($"{testPath}\\output.json"); var armTemplate = JsonConvert.DeserializeObject>(output); JObject parameters = (JObject)armTemplate["parameters"]; - Assert.AreEqual("TestRG", parameters.GetValue("Group")["value"].Value()); - Assert.AreEqual("Prod", parameters.GetValue("Environment")["value"].Value()); - Assert.AreEqual("String", parameters.GetValue("options")["value"].First()["source"].Type.ToString()); - Assert.AreEqual("file1", parameters.GetValue("options")["value"].First()["source"].Value()); - Assert.AreEqual("Boolean", parameters.GetValue("options")["value"].First()["isEnabled"].Type.ToString()); - Assert.AreEqual(true, parameters.GetValue("options")["value"].First()["isEnabled"].Value()); + Assert.AreEqual("TestRG", parameters.GetValue("Group", comparison)["value"].Value()); + Assert.AreEqual("Prod", parameters.GetValue("Environment", comparison)["value"].Value()); + Assert.AreEqual("String", parameters.GetValue("options", comparison)["value"].First()["source"].Type.ToString()); + Assert.AreEqual("file1", parameters.GetValue("options", comparison)["value"].First()["source"].Value()); + Assert.AreEqual("Boolean", parameters.GetValue("options", comparison)["value"].First()["isEnabled"].Type.ToString()); + Assert.AreEqual(true, parameters.GetValue("options", comparison)["value"].First()["isEnabled"].Value()); } [TestMethod] @@ -392,10 +393,10 @@ public void ShouldIncludeAdditionalPropertiesInArmParameterFile() var task = new AggregateConfig(mockFileSystem) { - InputType = InputTypeEnum.Arm.ToString(), + InputType = InputType.Arm.ToString(), InputDirectory = testPath, OutputFile = testPath + @"\output.parameters.json", - OutputType = OutputTypeEnum.Arm.ToString(), + OutputType = OutputType.Arm.ToString(), AddSourceProperty = true, AdditionalProperties = new Dictionary { @@ -413,12 +414,12 @@ public void ShouldIncludeAdditionalPropertiesInArmParameterFile() string output = mockFileSystem.ReadAllText($"{testPath}\\output.parameters.json"); var armTemplate = JsonConvert.DeserializeObject>(output); JObject parameters = (JObject)armTemplate["parameters"]; - Assert.AreEqual("TestRG", parameters.GetValue("Group")["value"].Value()); - Assert.AreEqual("Prod", parameters.GetValue("Environment")["value"].Value()); - Assert.AreEqual("String", parameters.GetValue("options")["value"].First()["source"].Type.ToString()); - Assert.AreEqual("file1.parameters", parameters.GetValue("options")["value"].First()["source"].Value()); - Assert.AreEqual("Boolean", parameters.GetValue("options")["value"].First()["isEnabled"].Type.ToString()); - Assert.AreEqual(true, parameters.GetValue("options")["value"].First()["isEnabled"].Value()); + Assert.AreEqual("TestRG", parameters.GetValue("Group", comparison)["value"].Value()); + Assert.AreEqual("Prod", parameters.GetValue("Environment", comparison)["value"].Value()); + Assert.AreEqual("String", parameters.GetValue("options", comparison)["value"].First()["source"].Type.ToString()); + Assert.AreEqual("file1.parameters", parameters.GetValue("options", comparison)["value"].First()["source"].Value()); + Assert.AreEqual("Boolean", parameters.GetValue("options", comparison)["value"].First()["isEnabled"].Type.ToString()); + Assert.AreEqual(true, parameters.GetValue("options", comparison)["value"].First()["isEnabled"].Value()); } [TestMethod] @@ -449,7 +450,7 @@ public void StressTest_ShouldAddSourcePropertyManyFiles() { InputDirectory = testPath, OutputFile = testPath + @"\output.json", - OutputType = OutputTypeEnum.Json.ToString(), + OutputType = OutputType.Json.ToString(), AddSourceProperty = true, BuildEngine = Mock.Of() }; diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 365f37c..ae115c3 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -7,7 +7,7 @@ disable false true - CS1591 + CS1591,CA1707,CA5394,CA1305 diff --git a/src/UnitTests/VirtualFileSystem.cs b/src/UnitTests/VirtualFileSystem.cs index c4f5654..973a546 100644 --- a/src/UnitTests/VirtualFileSystem.cs +++ b/src/UnitTests/VirtualFileSystem.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Text; @@ -6,12 +7,11 @@ namespace AggregateConfigBuildTask.Tests.Unit { - internal class VirtualFileSystem(bool isWindowsMode = true) : IFileSystem + internal sealed class VirtualFileSystem(bool isWindowsMode = true) : IFileSystem { private readonly bool isWindowsMode = isWindowsMode; - private readonly Dictionary fileSystem = new( - isWindowsMode ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal - ); + private ConcurrentDictionary fileSystem = new( + isWindowsMode ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); private RegexOptions RegexOptions => isWindowsMode ? RegexOptions.IgnoreCase : RegexOptions.None; private StringComparison StringComparison => isWindowsMode ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; @@ -132,6 +132,14 @@ public Stream OpenRead(string inputPath) return new MemoryStream(byteArray); } + /// + /// Delete all files on the virtual file system. + /// + public void FormatSystem() + { + fileSystem = new ConcurrentDictionary(); + } + /// /// Ensures that the provided directory path ends with a directory separator character. /// @@ -146,7 +154,7 @@ private string EnsureTrailingDirectorySeparator(string directoryPath) char directorySeparator = isWindowsMode ? '\\' : '/'; // Ensure the directory path ends with the correct directory separator - if (!directoryPath.EndsWith(directorySeparator.ToString())) + if (!directoryPath.EndsWith(directorySeparator.ToString(), StringComparison.Ordinal)) { directoryPath += directorySeparator; } @@ -163,7 +171,7 @@ private static string ConvertPatternToRegex(string searchPattern) string escapedPattern = Regex.Escape(searchPattern); // Replace escaped * and ? with their regex equivalents - escapedPattern = escapedPattern.Replace("\\*", ".*").Replace("\\?", "."); + escapedPattern = escapedPattern.Replace("\\*", ".*", StringComparison.Ordinal).Replace("\\?", ".", StringComparison.Ordinal); // Add start and end anchors to ensure full-string match return "^" + escapedPattern + "$"; @@ -185,7 +193,7 @@ private string NormalizePath(string path) string normalizedPath = path.Replace(alternativeSeparator, directorySeparator); // Trim any redundant trailing slashes except for root directory ("/" or "C:\\") - if (normalizedPath.Length > 1 && normalizedPath.EndsWith(directorySeparator.ToString())) + if (normalizedPath.Length > 1 && normalizedPath.EndsWith(directorySeparator.ToString(), StringComparison.Ordinal)) { normalizedPath = normalizedPath.TrimEnd(directorySeparator); }