diff --git a/Benchmarks/AnalyzeBenchmark.cs b/AppInspector.Benchmarks/AnalyzeBenchmark.cs similarity index 100% rename from Benchmarks/AnalyzeBenchmark.cs rename to AppInspector.Benchmarks/AnalyzeBenchmark.cs diff --git a/Benchmarks/Benchmarks.csproj b/AppInspector.Benchmarks/AppInspector.Benchmarks.csproj similarity index 82% rename from Benchmarks/Benchmarks.csproj rename to AppInspector.Benchmarks/AppInspector.Benchmarks.csproj index 175b705a..3beb2e71 100644 --- a/Benchmarks/Benchmarks.csproj +++ b/AppInspector.Benchmarks/AppInspector.Benchmarks.csproj @@ -5,6 +5,8 @@ net6.0;net7.0 enable 10.0 + ApplicationInspector.Benchmarks + Microsoft Corporation @@ -13,6 +15,7 @@ + diff --git a/Benchmarks/DistinctBenchmarks.cs b/AppInspector.Benchmarks/DistinctBenchmarks.cs similarity index 100% rename from Benchmarks/DistinctBenchmarks.cs rename to AppInspector.Benchmarks/DistinctBenchmarks.cs diff --git a/Benchmarks/Program.cs b/AppInspector.Benchmarks/Program.cs similarity index 84% rename from Benchmarks/Program.cs rename to AppInspector.Benchmarks/Program.cs index 088cce85..bb77cb6b 100644 --- a/Benchmarks/Program.cs +++ b/AppInspector.Benchmarks/Program.cs @@ -9,6 +9,6 @@ public static void Main(string[] args) { // new DebugInProcessConfig() //BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, new DebugInProcessConfig()); - var summary = BenchmarkRunner.Run(); + var summary = BenchmarkRunner.Run(); } } \ No newline at end of file diff --git a/AppInspector.Benchmarks/WriterBench.cs b/AppInspector.Benchmarks/WriterBench.cs new file mode 100644 index 00000000..ac3d8605 --- /dev/null +++ b/AppInspector.Benchmarks/WriterBench.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Reflection; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using DotLiquid; +using Microsoft.ApplicationInspector.CLI; +using Microsoft.ApplicationInspector.Commands; +using Microsoft.ApplicationInspector.RulesEngine; + +namespace ApplicationInspector.Benchmarks; +[MemoryDiagnoser] +[SimpleJob(RuntimeMoniker.Net70)] +public class WriterBench +{ + [Params(1000, 10000)] + public int N; + + // Holds the result object which will be serialized + private AnalyzeResult _result; + + [GlobalSetup] + public void GlobalSetup() + { + var _exerpt = "Hello World"; + var helper = new MetaDataHelper(".."); + var matchRecord = new MatchRecord("rule-id", "rule-name") + { + Boundary = new Boundary() { Index = 0, Length = 1 }, + EndLocationColumn = 0, + EndLocationLine = 1, + Excerpt = _exerpt, + FileName = "TestFile", + LanguageInfo = new LanguageInfo(), + Tags = new []{"TestTag"} + }; + for (int i = 0; i < N; i++) + { + helper.AddMatchRecord(matchRecord); + } + helper.PrepareReport(); + + _result = new AnalyzeResult() { Metadata = helper.Metadata, ResultCode = 0 }; + } + + [Benchmark(Baseline = true)] + public void ExportRecordsToJson() + { + var tmpPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + CLIAnalyzeCmdOptions analyzeOpts = new CLIAnalyzeCmdOptions() + { + OutputFileFormat = "json", + OutputFilePath = tmpPath + }; + var writerFactory = new WriterFactory(); + var writer = writerFactory.GetWriter(analyzeOpts); + writer.WriteResults(_result,analyzeOpts); + File.Delete(tmpPath); + } + + public static string GetExecutingDirectoryName() + { + if (Assembly.GetEntryAssembly()?.GetName().CodeBase is string codeBaseLoc) + { + var location = new Uri(codeBaseLoc); + return new FileInfo(location.AbsolutePath).Directory?.FullName ?? string.Empty; + } + + return string.Empty; + } +} \ No newline at end of file diff --git a/AppInspector.CLI/TagInfo.cs b/AppInspector.CLI/TagInfo.cs index bb47bd64..3a94d546 100644 --- a/AppInspector.CLI/TagInfo.cs +++ b/AppInspector.CLI/TagInfo.cs @@ -3,10 +3,10 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; using System.Text.RegularExpressions; using DotLiquid; using Microsoft.ApplicationInspector.RulesEngine; -using Newtonsoft.Json; namespace Microsoft.ApplicationInspector.Commands; @@ -21,17 +21,18 @@ public enum tagInfoType allTags } - [JsonProperty(PropertyName = "type")] public tagInfoType Type; + [JsonPropertyName("type")] + public tagInfoType Type; public TagCategory() { Groups = new List(); } - [JsonProperty(PropertyName = "categoryName")] + [JsonPropertyName("categoryName")] public string? Name { get; set; } - [JsonProperty(PropertyName = "groups")] + [JsonPropertyName("groups")] public List? Groups { get; set; } } @@ -45,14 +46,14 @@ public TagGroup() Patterns = new List(); } - [JsonProperty(PropertyName = "title")] public string? Title { get; set; } + [JsonPropertyName("title")] public string? Title { get; set; } [JsonIgnore] public string? IconURL { get; set; } - [JsonProperty(PropertyName = "dataRef")] + [JsonPropertyName("dataRef")] public string? DataRef { get; set; } - [JsonProperty(PropertyName = "patterns")] + [JsonPropertyName("patterns")] public List? Patterns { get; set; } } @@ -61,7 +62,7 @@ public class TagSearchPattern : Drop private Regex? _expression; private string _searchPattern = ""; - [JsonProperty(PropertyName = "searchPattern")] + [JsonPropertyName("searchPattern")] public string SearchPattern { get => _searchPattern; @@ -85,19 +86,19 @@ public Regex Expression } } - [JsonProperty(PropertyName = "displayName")] + [JsonPropertyName("displayName")] public string? DisplayName { get; set; } - [JsonProperty(PropertyName = "detectedIcon")] + [JsonPropertyName("detectedIcon")] public string? DetectedIcon { get; set; } = "fas fa-cat"; //default - [JsonProperty(PropertyName = "notDetectedIcon")] + [JsonPropertyName("notDetectedIcon")] public string? NotDetectedIcon { get; set; } - [JsonProperty(PropertyName = "detected")] + [JsonPropertyName("detected")] public bool Detected { get; set; } - [JsonProperty(PropertyName = "details")] + [JsonPropertyName("details")] public string Details { get @@ -107,7 +108,7 @@ public string Details } } - [JsonProperty(PropertyName = "confidence")] + [JsonPropertyName("confidence")] public string Confidence { get; set; } = "Medium"; public static bool ShouldSerializeExpression() @@ -123,14 +124,14 @@ public class TagInfo : Drop { private string _confidence = "Medium"; - [JsonProperty(PropertyName = "tag")] public string? Tag { get; set; } + [JsonPropertyName("tag")] public string? Tag { get; set; } - [JsonProperty(PropertyName = "displayName")] + [JsonPropertyName("displayName")] public string? ShortTag { get; set; } [JsonIgnore] public string? StatusIcon { get; set; } - [JsonProperty(PropertyName = "confidence")] + [JsonPropertyName("confidence")] public string Confidence { get => _confidence; @@ -143,14 +144,14 @@ public string Confidence } } - [JsonProperty(PropertyName = "severity")] + [JsonPropertyName("severity")] public string Severity { get; set; } = "Moderate"; - [JsonProperty(PropertyName = "detected")] + [JsonPropertyName("detected")] public bool Detected { get; set; } } public class TagException { - [JsonProperty(PropertyName = "tag")] public string? Tag { get; set; } + [JsonPropertyName("tag")] public string? Tag { get; set; } } \ No newline at end of file diff --git a/AppInspector.CLI/Writers/AnalyzeHtmlWriter.cs b/AppInspector.CLI/Writers/AnalyzeHtmlWriter.cs index 835e46c2..6944fa0e 100644 --- a/AppInspector.CLI/Writers/AnalyzeHtmlWriter.cs +++ b/AppInspector.CLI/Writers/AnalyzeHtmlWriter.cs @@ -16,7 +16,8 @@ using Microsoft.ApplicationInspector.RulesEngine; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; +using System.Text.Json.Serialization; +using System.Text.Json; namespace Microsoft.ApplicationInspector.CLI; @@ -27,7 +28,7 @@ public class AnalyzeHtmlWriter : CommandResultsWriter private MetaData? _appMetaData; - public AnalyzeHtmlWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter) + public AnalyzeHtmlWriter(StreamWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter) { _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; KeyedTagInfoLists = new Dictionary>(); @@ -91,7 +92,7 @@ private void WriteHtmlResult() string? jsonData; try { - jsonData = JsonConvert.SerializeObject(data); + jsonData = JsonSerializer.Serialize(data); } catch (Exception e) { @@ -232,9 +233,23 @@ public void PopulateTagGroups() //read default/user preferences on what tags to report presence on and groupings if (File.Exists(Utils.GetPath(Utils.AppPath.tagGroupPref))) { - TagGroupPreferences = - JsonConvert.DeserializeObject>( - File.ReadAllText(Utils.GetPath(Utils.AppPath.tagGroupPref))); + try + { + var options = new JsonSerializerOptions + { + ReadCommentHandling = JsonCommentHandling.Skip, // Allow strings to start with '/', e.g., "// Copyright (C) Microsoft. All rights reserved" + }; + TagGroupPreferences = + JsonSerializer.Deserialize>( + File.ReadAllText(Utils.GetPath(Utils.AppPath.tagGroupPref)), options); + } + catch (Exception e) + { + _logger.LogError( + "Failed to populate tag groups. Failed to serialize JSON representation of results in memory. {Type} : {Message}", + e.GetType().Name, e.Message); + throw; + } } else { @@ -627,13 +642,15 @@ public List ConvertTagCounters(IEnumerable metri /// public class TagCounterUI : Drop { - [JsonProperty(PropertyName = "tag")] public string? Tag { get; set; } + [JsonPropertyName("tag")] + public string? Tag { get; set; } - [JsonProperty(PropertyName = "displayName")] + [JsonPropertyName("displayName")] public string? ShortTag { get; set; } - [JsonProperty(PropertyName = "count")] public int Count { get; set; } + [JsonPropertyName("count")] + public int Count { get; set; } - [JsonProperty(PropertyName = "includeAsMatch")] + [JsonPropertyName("includeAsMatch")] public bool IncludeAsMatch => false; } \ No newline at end of file diff --git a/AppInspector.CLI/Writers/AnalyzeJsonWriter.cs b/AppInspector.CLI/Writers/AnalyzeJsonWriter.cs index eb317ef0..39621e41 100644 --- a/AppInspector.CLI/Writers/AnalyzeJsonWriter.cs +++ b/AppInspector.CLI/Writers/AnalyzeJsonWriter.cs @@ -5,7 +5,9 @@ using Microsoft.ApplicationInspector.Commands; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; +using System.Text.Json.Serialization; +using System.Text.Json; +using System; namespace Microsoft.ApplicationInspector.CLI; @@ -20,7 +22,7 @@ public class AnalyzeJsonWriter : CommandResultsWriter { private readonly ILogger _logger; - public AnalyzeJsonWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter) + public AnalyzeJsonWriter(StreamWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter) { _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; } @@ -28,12 +30,25 @@ public AnalyzeJsonWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true) { var analyzeResult = (AnalyzeResult)result; + if (StreamWriter == null) + { + throw new ArgumentNullException(nameof(StreamWriter)); + } - JsonSerializer jsonSerializer = new(); - jsonSerializer.Formatting = Formatting.Indented; - if (TextWriter != null) + try + { + var options = new JsonSerializerOptions + { + WriteIndented = true, + }; + JsonSerializer.Serialize(StreamWriter.BaseStream, analyzeResult, options); + } + catch (Exception e) { - jsonSerializer.Serialize(TextWriter, analyzeResult); + _logger.LogError( + "Failed to serialize JSON representation of results in memory. {Type} : {Message}", + e.GetType().Name, e.Message); + throw; } if (autoClose) @@ -47,6 +62,7 @@ public override void WriteResults(Result result, CLICommandOptions commandOption /// private class TagsFile { - [JsonProperty(PropertyName = "tags")] public string[]? Tags { get; set; } + [JsonPropertyName("tags")] + public string[]? Tags { get; set; } } } \ No newline at end of file diff --git a/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs b/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs index 2c2a019b..37adfca2 100644 --- a/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs +++ b/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs @@ -5,13 +5,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; using Microsoft.ApplicationInspector.Commands; using Microsoft.ApplicationInspector.RulesEngine; using Microsoft.CodeAnalysis.Sarif; using Microsoft.CST.OAT.Utils; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; using Location = Microsoft.CodeAnalysis.Sarif.Location; using Result = Microsoft.ApplicationInspector.Commands.Result; @@ -37,16 +37,16 @@ public class AnalyzeSarifWriter : CommandResultsWriter { private readonly ILogger _logger; - public AnalyzeSarifWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter) + public AnalyzeSarifWriter(StreamWriter streamWriter, ILoggerFactory? loggerFactory = null) : base(streamWriter) { _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; } public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true) { - if (TextWriter is null) + if (StreamWriter is null) { - throw new NullReferenceException(nameof(TextWriter)); + throw new ArgumentNullException(nameof(StreamWriter)); } string? basePath = null; @@ -186,11 +186,22 @@ public override void WriteResults(Result result, CLICommandOptions commandOption run.Results.Add(sarifResult); } - log.Runs.Add(run); - JsonSerializerSettings serializerSettings = new(); - var serializer = new JsonSerializer(); - serializer.Serialize(TextWriter, log); - FlushAndClose(); + log.Runs.Add(run); + try + { + JsonSerializer.Serialize(StreamWriter.BaseStream, log); + } + catch (Exception e) + { + _logger.LogError( + "Failed to serialize JSON representation of results in memory. {Type} : {Message}", + e.GetType().Name, e.Message); + throw; + } + if (autoClose) + { + FlushAndClose(); + } } else { diff --git a/AppInspector.CLI/Writers/CmdResultsWriter.cs b/AppInspector.CLI/Writers/CmdResultsWriter.cs index 96889927..13200b13 100644 --- a/AppInspector.CLI/Writers/CmdResultsWriter.cs +++ b/AppInspector.CLI/Writers/CmdResultsWriter.cs @@ -17,6 +17,12 @@ protected CommandResultsWriter(TextWriter writer) } public TextWriter TextWriter { get; } + + public StreamWriter? StreamWriter + { + get { return (StreamWriter)TextWriter; } + } + public abstract void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true); public void FlushAndClose() diff --git a/AppInspector.CLI/Writers/JsonWriter.cs b/AppInspector.CLI/Writers/JsonWriter.cs index 9b310fab..7cb8f194 100644 --- a/AppInspector.CLI/Writers/JsonWriter.cs +++ b/AppInspector.CLI/Writers/JsonWriter.cs @@ -1,9 +1,11 @@ using System; using System.IO; +using System.Net.Sockets; +using System.Text.Json; +using System.Text.Json.Serialization; using Microsoft.ApplicationInspector.Commands; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; namespace Microsoft.ApplicationInspector.CLI.Writers; @@ -11,35 +13,48 @@ internal class JsonWriter : CommandResultsWriter { private readonly ILogger _logger; - internal JsonWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter) + internal JsonWriter(StreamWriter streamWriter, ILoggerFactory? loggerFactory = null) : base(streamWriter) { _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; } public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true) { - JsonSerializer jsonSerializer = new(); - jsonSerializer.Formatting = Formatting.Indented; - jsonSerializer.NullValueHandling = NullValueHandling.Ignore; - jsonSerializer.DefaultValueHandling = DefaultValueHandling.Ignore; + var options = new JsonSerializerOptions + { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, // The WhenWritingDefault setting also prevents serialization of null-value reference type and nullable value type properties. + // jsonSerializer.NullValueHandling = NullValueHandling.Ignore; + // jsonSerializer.DefaultValueHandling = DefaultValueHandling.Ignore; + }; - if (TextWriter is null) + if (StreamWriter is null) { throw new ArgumentNullException(nameof(TextWriter)); } - switch (result) + try + { + switch (result) + { + case TagDiffResult: + case ExportTagsResult: + case VerifyRulesResult: + JsonSerializer.Serialize(StreamWriter.BaseStream, result, options); + break; + case PackRulesResult prr: + JsonSerializer.Serialize(StreamWriter.BaseStream, prr.Rules, options); + break; + default: + throw new Exception("Unexpected object type for json writer"); + } + } + catch (Exception e) { - case TagDiffResult: - case ExportTagsResult: - case VerifyRulesResult: - jsonSerializer.Serialize(TextWriter, result); - break; - case PackRulesResult prr: - jsonSerializer.Serialize(TextWriter, prr.Rules); - break; - default: - throw new Exception("Unexpected object type for json writer"); + _logger.LogError( + "Failed to serialize JSON representation of results in memory. {Type} : {Message}", + e.GetType().Name, e.Message); + throw; } if (autoClose) diff --git a/AppInspector.CLI/Writers/WriterFactory.cs b/AppInspector.CLI/Writers/WriterFactory.cs index 2c094ce2..5c6a1241 100644 --- a/AppInspector.CLI/Writers/WriterFactory.cs +++ b/AppInspector.CLI/Writers/WriterFactory.cs @@ -49,21 +49,21 @@ public CommandResultsWriter GetWriter(CLICommandOptions options) /// /// private CommandResultsWriter GetAnalyzeWriter(CLIAnalyzeCmdOptions options) - { - var textWriter = GetTextWriter(options.OutputFilePath); + { + var streamWriter = GetStreamWriter(options.OutputFilePath); return options.OutputFileFormat.ToLower() switch { - "json" => new AnalyzeJsonWriter(textWriter, _loggerFactory), - "text" => new AnalyzeTextWriter(textWriter, options.TextOutputFormat, _loggerFactory), - "html" => new AnalyzeHtmlWriter(textWriter, _loggerFactory), - "sarif" => new AnalyzeSarifWriter(textWriter, _loggerFactory), + "json" => new AnalyzeJsonWriter(streamWriter, _loggerFactory), + "text" => new AnalyzeTextWriter(streamWriter, options.TextOutputFormat, _loggerFactory), + "html" => new AnalyzeHtmlWriter(streamWriter, _loggerFactory), + "sarif" => new AnalyzeSarifWriter(streamWriter, _loggerFactory), _ => throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_ARG_VALUE, "-f")) }; } public CommandResultsWriter GetExportTagsWriter(CLIExportTagsCmdOptions options) { - var writer = GetTextWriter(options.OutputFilePath); + var writer = GetStreamWriter(options.OutputFilePath); return options.OutputFileFormat.ToLower() switch { "json" => new JsonWriter(writer, _loggerFactory), @@ -74,7 +74,7 @@ public CommandResultsWriter GetExportTagsWriter(CLIExportTagsCmdOptions options) private CommandResultsWriter GetTagDiffWriter(CLITagDiffCmdOptions options) { - var writer = GetTextWriter(options.OutputFilePath); + var writer = GetStreamWriter(options.OutputFilePath); return options.OutputFileFormat.ToLower() switch { "json" => new JsonWriter(writer, _loggerFactory), @@ -85,7 +85,7 @@ private CommandResultsWriter GetTagDiffWriter(CLITagDiffCmdOptions options) private CommandResultsWriter GetVerifyRulesWriter(CLIVerifyRulesCmdOptions options) { - var writer = GetTextWriter(options.OutputFilePath); + var writer = GetStreamWriter(options.OutputFilePath); return options.OutputFileFormat.ToLower() switch { "json" => new JsonWriter(writer, _loggerFactory), @@ -96,7 +96,7 @@ private CommandResultsWriter GetVerifyRulesWriter(CLIVerifyRulesCmdOptions optio private CommandResultsWriter GetPackRulesWriter(CLIPackRulesCmdOptions options) { - var writer = GetTextWriter(options.OutputFilePath); + var writer = GetStreamWriter(options.OutputFilePath); return options.OutputFileFormat.ToLower() switch { "json" => new JsonWriter(writer, _loggerFactory), @@ -109,6 +109,7 @@ private CommandResultsWriter GetPackRulesWriter(CLIPackRulesCmdOptions options) /// /// The path to create, if null or empty will use Console.Out. /// + [Obsolete("Use GetStreamWriter instead.")] private TextWriter GetTextWriter(string? outputFileName) { TextWriter textWriter; @@ -131,4 +132,34 @@ private TextWriter GetTextWriter(string? outputFileName) return textWriter; } + + /// + /// Create a StreamWriter for the given path or console. + /// + /// The path to create, if null or empty will use console output. + /// + private StreamWriter GetStreamWriter(string? outputFileName) + { + StreamWriter streamWriter; + if (string.IsNullOrEmpty(outputFileName)) + { + streamWriter = new StreamWriter(Console.OpenStandardOutput()); + streamWriter.AutoFlush = true; + Console.SetOut(streamWriter); + } + else + { + try + { + streamWriter = File.CreateText(outputFileName); + } + catch (Exception) + { + _logger.LogError(MsgHelp.GetString(MsgHelp.ID.CMD_INVALID_FILE_OR_DIR), outputFileName); + throw; + } + } + + return streamWriter; + } } \ No newline at end of file diff --git a/AppInspector.RulesEngine/AppInspector.RulesEngine.csproj b/AppInspector.RulesEngine/AppInspector.RulesEngine.csproj index 3762de1b..0da94793 100644 --- a/AppInspector.RulesEngine/AppInspector.RulesEngine.csproj +++ b/AppInspector.RulesEngine/AppInspector.RulesEngine.csproj @@ -35,7 +35,6 @@ - diff --git a/AppInspector.RulesEngine/Comment.cs b/AppInspector.RulesEngine/Comment.cs index 2e08a05f..fe795960 100644 --- a/AppInspector.RulesEngine/Comment.cs +++ b/AppInspector.RulesEngine/Comment.cs @@ -1,7 +1,7 @@ // Copyright(C) Microsoft.All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.RulesEngine; @@ -10,15 +10,15 @@ namespace Microsoft.ApplicationInspector.RulesEngine; /// internal class Comment { - [JsonProperty(PropertyName = "language")] + [JsonPropertyName("language")] public string[]? Languages { get; set; } - [JsonProperty(PropertyName = "inline")] + [JsonPropertyName("inline")] public string? Inline { get; set; } - [JsonProperty(PropertyName = "prefix")] + [JsonPropertyName("prefix")] public string? Prefix { get; set; } - [JsonProperty(PropertyName = "suffix")] + [JsonPropertyName("suffix")] public string? Suffix { get; set; } } \ No newline at end of file diff --git a/AppInspector.RulesEngine/Confidence.cs b/AppInspector.RulesEngine/Confidence.cs index 654aeabe..960774f4 100644 --- a/AppInspector.RulesEngine/Confidence.cs +++ b/AppInspector.RulesEngine/Confidence.cs @@ -1,11 +1,10 @@ using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.RulesEngine; [Flags] -[JsonConverter(typeof(StringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum Confidence { Unspecified = 0, diff --git a/AppInspector.RulesEngine/LanguageInfo.cs b/AppInspector.RulesEngine/LanguageInfo.cs index 82b449f6..13f6ccfe 100644 --- a/AppInspector.RulesEngine/LanguageInfo.cs +++ b/AppInspector.RulesEngine/LanguageInfo.cs @@ -1,8 +1,7 @@ // Copyright(C) Microsoft.All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.RulesEngine; @@ -17,15 +16,16 @@ public enum LangFileType Build } - [JsonProperty(PropertyName = "name")] public string Name { get; set; } = ""; + [JsonPropertyName("name")] + public string Name { get; set; } = ""; - [JsonProperty(PropertyName = "extensions")] + [JsonPropertyName("extensions")] public string[]? Extensions { get; set; } - [JsonProperty(PropertyName = "file-names")] + [JsonPropertyName("file-names")] public string[]? FileNames { get; set; } - [JsonProperty(PropertyName = "type")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("type")] + [JsonConverter(typeof(JsonStringEnumConverter))] public LangFileType Type { get; set; } = LangFileType.Code; } \ No newline at end of file diff --git a/AppInspector.RulesEngine/Languages.cs b/AppInspector.RulesEngine/Languages.cs index 04b2da6d..51f8d9da 100644 --- a/AppInspector.RulesEngine/Languages.cs +++ b/AppInspector.RulesEngine/Languages.cs @@ -5,9 +5,9 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; namespace Microsoft.ApplicationInspector.RulesEngine; @@ -18,8 +18,8 @@ public sealed class Languages { private const string CommentResourcePath = "Microsoft.ApplicationInspector.RulesEngine.Resources.comments.json"; private const string LanguagesResourcePath = "Microsoft.ApplicationInspector.RulesEngine.Resources.languages.json"; - private readonly List _comments; - private readonly List _languageInfos; + private readonly List _comments = new(); + private readonly List _languageInfos = new(); private readonly ILogger _logger; public Languages(ILoggerFactory? loggerFactory = null, Stream? commentsStream = null, @@ -33,14 +33,14 @@ public Languages(ILoggerFactory? loggerFactory = null, Stream? commentsStream = { _logger.LogError("Failed to load embedded comments configuration from {CommentResourcePath}", CommentResourcePath); - _comments = new List(); } else { - using StreamReader commentStreamReader = new(commentResource); - using JsonReader commentJsonReader = new JsonTextReader(commentStreamReader); - JsonSerializer jsonSerializer = new(); - _comments = jsonSerializer.Deserialize>(commentJsonReader) ?? new List(); + var result = JsonSerializer.DeserializeAsync>(commentResource); + if (result.Result != null) + { + _comments = result.Result.ToList(); + } } var languagesResource = languagesStream ?? assembly.GetManifestResourceStream(LanguagesResourcePath); @@ -48,15 +48,14 @@ public Languages(ILoggerFactory? loggerFactory = null, Stream? commentsStream = { _logger.LogError("Failed to load embedded languages configuration from {LanguagesResourcePath}", LanguagesResourcePath); - _languageInfos = new List(); } else { - using StreamReader languagesStreamReader = new(languagesResource); - using JsonReader languagesJsonReader = new JsonTextReader(languagesStreamReader); - JsonSerializer jsonSerializer = new(); - _languageInfos = jsonSerializer.Deserialize>(languagesJsonReader) ?? - new List(); + var result = JsonSerializer.DeserializeAsync>(languagesResource); + if (result.Result != null) + { + _languageInfos = result.Result.ToList(); + } } } diff --git a/AppInspector.RulesEngine/MatchRecord.cs b/AppInspector.RulesEngine/MatchRecord.cs index ad834c5f..fd69bc88 100644 --- a/AppInspector.RulesEngine/MatchRecord.cs +++ b/AppInspector.RulesEngine/MatchRecord.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. See LICENSE.txt in the project root for license information. using System.Diagnostics.CodeAnalysis; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.RulesEngine; @@ -37,28 +37,28 @@ public MatchRecord(string ruleId, string ruleName) /// /// Rule Id found in matching rule /// - [JsonProperty(PropertyName = "ruleId")] + [JsonPropertyName("ruleId")] [ExcludeFromCodeCoverage] public string RuleId { get; set; } /// /// Rule name found in matching rule /// - [JsonProperty(PropertyName = "ruleName")] + [JsonPropertyName("ruleName")] [ExcludeFromCodeCoverage] public string RuleName { get; set; } /// /// Rule description found in matching rule /// - [JsonProperty(PropertyName = "ruleDescription")] + [JsonPropertyName("ruleDescription")] [ExcludeFromCodeCoverage] public string? RuleDescription { get; set; } /// /// Tags in matching rule /// - [JsonProperty(PropertyName = "tags")] + [JsonPropertyName("tags")] [ExcludeFromCodeCoverage] public string[]? Tags { get; set; } @@ -66,7 +66,7 @@ public MatchRecord(string ruleId, string ruleName) /// Rule severity /// /// _rule - [JsonProperty(PropertyName = "severity")] + [JsonPropertyName("severity")] [ExcludeFromCodeCoverage] public Severity Severity { get; set; } @@ -75,25 +75,26 @@ public MatchRecord(string ruleId, string ruleName) /// /// Matching pattern found in matching rule /// - [JsonProperty(PropertyName = "pattern")] + [JsonPropertyName("pattern")] [ExcludeFromCodeCoverage] public string? Pattern => MatchingPattern?.Pattern; /// /// Pattern confidence in matching rule pattern /// - [JsonProperty(PropertyName = "confidence")] + [JsonPropertyName("confidence")] [ExcludeFromCodeCoverage] public Confidence Confidence => MatchingPattern?.Confidence ?? Confidence.Unspecified; /// /// Pattern type of matching pattern /// - [JsonProperty(PropertyName = "type")] + [JsonPropertyName("type")] [ExcludeFromCodeCoverage] public string? PatternType => MatchingPattern?.PatternType.ToString(); - [JsonIgnore] [ExcludeFromCodeCoverage] public TextContainer? FullTextContainer { get; set; } + [JsonIgnore] [ExcludeFromCodeCoverage] + public TextContainer? FullTextContainer { get; set; } /// /// Internal to namespace only @@ -105,27 +106,27 @@ public MatchRecord(string ruleId, string ruleName) /// /// Friendly source type /// - [JsonProperty(PropertyName = "language")] + [JsonPropertyName("language")] public string? Language => LanguageInfo?.Name; /// /// Filename of this match /// - [JsonProperty(PropertyName = "fileName")] + [JsonPropertyName("fileName")] [ExcludeFromCodeCoverage] public string? FileName { get; set; } /// /// Matching text for this record /// - [JsonProperty(PropertyName = "sample")] + [JsonPropertyName("sample")] [ExcludeFromCodeCoverage] public string Sample { get; set; } = ""; /// /// Matching surrounding context text for sample in this record /// - [JsonProperty(PropertyName = "excerpt")] + [JsonPropertyName("excerpt")] [ExcludeFromCodeCoverage] public string Excerpt { get; set; } = ""; @@ -134,28 +135,28 @@ public MatchRecord(string ruleId, string ruleName) /// /// Starting line location of the matching text /// - [JsonProperty(PropertyName = "startLocationLine")] + [JsonPropertyName("startLocationLine")] [ExcludeFromCodeCoverage] public int StartLocationLine { get; set; } /// /// Starting column location of the matching text /// - [JsonProperty(PropertyName = "startLocationColumn")] + [JsonPropertyName("startLocationColumn")] [ExcludeFromCodeCoverage] public int StartLocationColumn { get; set; } /// /// Ending line location of the matching text /// - [JsonProperty(PropertyName = "endLocationLine")] + [JsonPropertyName("endLocationLine")] [ExcludeFromCodeCoverage] public int EndLocationLine { get; set; } /// /// Ending column of the matching text /// - [JsonProperty(PropertyName = "endLocationColumn")] + [JsonPropertyName("endLocationColumn")] [ExcludeFromCodeCoverage] public int EndLocationColumn { get; set; } } \ No newline at end of file diff --git a/AppInspector.RulesEngine/PatternScope.cs b/AppInspector.RulesEngine/PatternScope.cs index b79479ca..d39dded1 100644 --- a/AppInspector.RulesEngine/PatternScope.cs +++ b/AppInspector.RulesEngine/PatternScope.cs @@ -1,11 +1,10 @@ // Copyright (C) Microsoft. All rights reserved. Licensed under the MIT License. -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.RulesEngine; -[JsonConverter(typeof(StringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum PatternScope { All, diff --git a/AppInspector.RulesEngine/PatternType.cs b/AppInspector.RulesEngine/PatternType.cs index d4280500..8739bd00 100644 --- a/AppInspector.RulesEngine/PatternType.cs +++ b/AppInspector.RulesEngine/PatternType.cs @@ -1,14 +1,13 @@ // Copyright (C) Microsoft. All rights reserved. Licensed under the MIT License. -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.RulesEngine; /// /// Pattern Type for search pattern /// -[JsonConverter(typeof(StringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum PatternType { Regex, diff --git a/AppInspector.RulesEngine/Rule.cs b/AppInspector.RulesEngine/Rule.cs index cb13591a..0b95091b 100644 --- a/AppInspector.RulesEngine/Rule.cs +++ b/AppInspector.RulesEngine/Rule.cs @@ -4,9 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; namespace Microsoft.ApplicationInspector.RulesEngine; @@ -39,20 +38,20 @@ public class Rule [JsonIgnore] public bool Disabled { get; set; } - [JsonProperty(PropertyName = "name")] public string Name { get; set; } = ""; + [JsonPropertyName("name")] public string Name { get; set; } = ""; - [JsonProperty(PropertyName = "id")] public string Id { get; set; } = ""; + [JsonPropertyName("id")] public string Id { get; set; } = ""; - [JsonProperty(PropertyName = "description")] + [JsonPropertyName("description")] public string? Description { get; set; } = ""; - [JsonProperty(PropertyName = "does_not_apply_to")] + [JsonPropertyName("does_not_apply_to")] public List? DoesNotApplyTo { get; set; } - [JsonProperty(PropertyName = "applies_to")] + [JsonPropertyName("applies_to")] public string[]? AppliesTo { get; set; } - [JsonProperty(PropertyName = "applies_to_file_regex")] + [JsonPropertyName("applies_to_file_regex")] public string[]? FileRegexes { get => _fileRegexes; @@ -78,24 +77,24 @@ public IEnumerable CompiledFileRegexes } } - [JsonProperty(PropertyName = "tags")] public string[]? Tags { get; set; } + [JsonPropertyName("tags")] public string[]? Tags { get; set; } - [JsonProperty(PropertyName = "severity")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("severity")] + [JsonConverter(typeof(JsonStringEnumConverter))] public Severity Severity { get; set; } = Severity.Moderate; - [JsonProperty(PropertyName = "overrides")] + [JsonPropertyName("overrides")] public string[]? Overrides { get; set; } - [JsonProperty(PropertyName = "patterns")] + [JsonPropertyName("patterns")] public SearchPattern[] Patterns { get; set; } = Array.Empty(); - [JsonProperty(PropertyName = "conditions")] + [JsonPropertyName("conditions")] public SearchCondition[]? Conditions { get; set; } - [JsonProperty(PropertyName = "must-match")] + [JsonPropertyName("must-match")] public string[]? MustMatch { get; set; } - [JsonProperty(PropertyName = "must-not-match")] + [JsonPropertyName("must-not-match")] public string[]? MustNotMatch { get; set; } } \ No newline at end of file diff --git a/AppInspector.RulesEngine/SearchCondition.cs b/AppInspector.RulesEngine/SearchCondition.cs index ca8b0737..63e7be14 100644 --- a/AppInspector.RulesEngine/SearchCondition.cs +++ b/AppInspector.RulesEngine/SearchCondition.cs @@ -1,17 +1,17 @@ // Copyright (C) Microsoft. All rights reserved. Licensed under the MIT License. -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.RulesEngine; public class SearchCondition { - [JsonProperty(PropertyName = "negate_finding")] + [JsonPropertyName("negate_finding")] public bool NegateFinding { get; set; } - [JsonProperty(PropertyName = "pattern")] + [JsonPropertyName("pattern")] public SearchPattern? Pattern { get; set; } - [JsonProperty(PropertyName = "search_in")] + [JsonPropertyName("search_in")] public string? SearchIn { get; set; } } \ No newline at end of file diff --git a/AppInspector.RulesEngine/SearchPattern.cs b/AppInspector.RulesEngine/SearchPattern.cs index 5c3ac882..898371db 100644 --- a/AppInspector.RulesEngine/SearchPattern.cs +++ b/AppInspector.RulesEngine/SearchPattern.cs @@ -1,9 +1,6 @@ // Copyright (C) Microsoft. All rights reserved. Licensed under the MIT License. -using System.Collections.Generic; -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.RulesEngine; @@ -12,41 +9,41 @@ namespace Microsoft.ApplicationInspector.RulesEngine; /// public class SearchPattern { - [JsonProperty(PropertyName = "confidence")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("confidence")] + [JsonConverter(typeof(JsonStringEnumConverter))] public Confidence Confidence { get; set; } - [JsonProperty(PropertyName = "modifiers")] + [JsonPropertyName("modifiers")] public string[]? Modifiers { get; set; } - [JsonProperty(PropertyName = "pattern")] + [JsonPropertyName("pattern")] public string? Pattern { get; set; } - [JsonProperty(PropertyName = "type")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("type")] + [JsonConverter(typeof(JsonStringEnumConverter))] public PatternType? PatternType { get; set; } - [JsonProperty(PropertyName = "scopes")] + [JsonPropertyName("scopes")] public PatternScope[]? Scopes { get; set; } /// /// If set, attempt to parse the file as XML and if that is possible, /// before running the pattern, select down to the XPath provided /// - [JsonProperty(PropertyName = "xpaths")] + [JsonPropertyName("xpaths")] public string[]? XPaths { get; set; } /// /// If set, attempt to parse the file as JSON and if that is possible, /// before running the pattern, select down to the JsonPath provided /// - [JsonProperty(PropertyName = "jsonpaths")] + [JsonPropertyName("jsonpaths")] public string[]? JsonPaths { get; set; } /// /// If set, attempt to parse the file as YML and if that is possible, /// before running the pattern, select down to the JsonPath provided /// - [JsonProperty(PropertyName = "ymlpaths")] + [JsonPropertyName("ymlpaths")] public string[]? YamlPaths { get; set; } } \ No newline at end of file diff --git a/AppInspector.RulesEngine/Severity.cs b/AppInspector.RulesEngine/Severity.cs index b008398b..c1ecc81e 100644 --- a/AppInspector.RulesEngine/Severity.cs +++ b/AppInspector.RulesEngine/Severity.cs @@ -1,8 +1,7 @@ // Copyright (C) Microsoft. All rights reserved. Licensed under the MIT License. using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.RulesEngine; @@ -10,7 +9,7 @@ namespace Microsoft.ApplicationInspector.RulesEngine; /// Issue severity /// [Flags] -[JsonConverter(typeof(StringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum Severity { /// diff --git a/AppInspector.RulesEngine/TypedRuleSet.cs b/AppInspector.RulesEngine/TypedRuleSet.cs index da4712ae..a61ba47a 100644 --- a/AppInspector.RulesEngine/TypedRuleSet.cs +++ b/AppInspector.RulesEngine/TypedRuleSet.cs @@ -2,9 +2,9 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; namespace Microsoft.ApplicationInspector.RulesEngine; @@ -62,10 +62,6 @@ private IEnumerable AppInspectorRulesAsEnumerableT() /// Tag for the rules /// Thrown if the filename is null or empty /// Thrown if the specified file cannot be found on the file system - /// - /// Thrown if the specified file cannot be deserialized as a - /// - /// public void AddPath(string path, string? tag = null) { if (Directory.Exists(path)) @@ -88,11 +84,7 @@ public void AddPath(string path, string? tag = null) /// Path to rules folder /// Tag for the rules /// Thrown if the filename is null or empty - /// Thrown if the specified file cannot be found on the file system - /// - /// Thrown if the specified file cannot be deserialized as a - /// - /// + /// Thrown if the specified file cannot be found on the file system public void AddDirectory(string path, string? tag = null) { if (!Directory.Exists(path)) @@ -111,10 +103,6 @@ public void AddDirectory(string path, string? tag = null) /// Tag for the rules /// Thrown if the filename is null or empty /// Thrown if the specified file cannot be found on the file system - /// - /// Thrown if the specified file cannot be deserialized as a - /// - /// public void AddFile(string? filename, string? tag = null) { if (string.IsNullOrEmpty(filename)) @@ -180,18 +168,27 @@ public void AddRule(T rule) /// /// /// Add an additional tag to the rules when added. + /// + /// Thrown if the specified json string cannot be deserialized as a + /// + /// /// internal IEnumerable StringToRules(string jsonString, string sourceName, string? tag = null) { - List? ruleList = null; + List? ruleList; try { - ruleList = JsonConvert.DeserializeObject>(jsonString); + var options = new JsonSerializerOptions() + { + AllowTrailingCommas = true, + + }; + ruleList = JsonSerializer.Deserialize>(jsonString, options); } - catch (JsonSerializationException jsonSerializationException) + catch (JsonException jsonSerializationException) { _logger.LogError("Failed to deserialize '{0}' at Line {1} Column {2}", sourceName, - jsonSerializationException.LineNumber, jsonSerializationException.LinePosition); + jsonSerializationException.LineNumber, jsonSerializationException.BytePositionInLine); throw; } diff --git a/AppInspector.Tests/AppInspector.Tests.csproj b/AppInspector.Tests/AppInspector.Tests.csproj index 79872bbf..9bcc708f 100644 --- a/AppInspector.Tests/AppInspector.Tests.csproj +++ b/AppInspector.Tests/AppInspector.Tests.csproj @@ -28,6 +28,7 @@ + diff --git a/AppInspector.Tests/Languages/LanguagesTests.cs b/AppInspector.Tests/Languages/LanguagesTests.cs index d7c8332d..219dba07 100644 --- a/AppInspector.Tests/Languages/LanguagesTests.cs +++ b/AppInspector.Tests/Languages/LanguagesTests.cs @@ -1,11 +1,11 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Text.Json; using Microsoft.ApplicationInspector.Logging; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json; using Serilog.Events; namespace AppInspector.Tests.Languages; @@ -88,10 +88,10 @@ public void DetectCustomLanguage() [TestMethod] public void EmptyLanguagesOnInvalidCommentsAndLanguages() { - Assert.ThrowsException(() => + Assert.ThrowsException(() => Microsoft.ApplicationInspector.RulesEngine.Languages.FromConfigurationFiles(_factory, invalidTestCommentsPath)); - Assert.ThrowsException(() => + Assert.ThrowsException(() => Microsoft.ApplicationInspector.RulesEngine.Languages.FromConfigurationFiles(_factory, null, invalidTestLanguagesPath)); } diff --git a/AppInspector.sln b/AppInspector.sln index ccbcd510..668e9517 100644 --- a/AppInspector.sln +++ b/AppInspector.sln @@ -15,7 +15,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppInspector.CLI", "AppInsp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppInspector.Tests", "AppInspector.Tests\AppInspector.Tests.csproj", "{181BD826-A428-41D9-8BEC-0D8EB2288DF5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{F031887C-EA60-4390-9940-765E99E69B8F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppInspector.Benchmarks", "AppInspector.Benchmarks\AppInspector.Benchmarks.csproj", "{F031887C-EA60-4390-9940-765E99E69B8F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppInspector.Common", "AppInspector.Common\AppInspector.Common.csproj", "{B15415B6-6EC8-4AA1-B8AF-DF0ABE5F16DB}" EndProject diff --git a/AppInspector/AppInspector.Commands.csproj b/AppInspector/AppInspector.Commands.csproj index ada00039..5ab338c2 100644 --- a/AppInspector/AppInspector.Commands.csproj +++ b/AppInspector/AppInspector.Commands.csproj @@ -57,7 +57,6 @@ - diff --git a/AppInspector/Commands/AnalyzeCommand.cs b/AppInspector/Commands/AnalyzeCommand.cs index 1615e2c7..f4e53dcf 100644 --- a/AppInspector/Commands/AnalyzeCommand.cs +++ b/AppInspector/Commands/AnalyzeCommand.cs @@ -16,7 +16,7 @@ using Microsoft.CST.RecursiveExtractor; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; +using System.Text.Json.Serialization; using ShellProgressBar; namespace Microsoft.ApplicationInspector.Commands; @@ -123,13 +123,15 @@ public AnalyzeResult() Metadata = new MetaData("", ""); //needed for serialization for other commands; replaced later } - [JsonProperty(Order = 2, PropertyName = "resultCode")] + [JsonPropertyName("resultCode")] + // [JsonPropertyOrder(2)] .NET 6.0 only public ExitCode ResultCode { get; set; } /// /// Analyze command result object containing scan properties /// - [JsonProperty(Order = 3, PropertyName = "metaData")] + [JsonPropertyName("metaData")] + // [JsonPropertyOrder(3)] public MetaData Metadata { get; set; } } diff --git a/AppInspector/Commands/ExportTagsCommand.cs b/AppInspector/Commands/ExportTagsCommand.cs index fa6bb735..9b03e974 100644 --- a/AppInspector/Commands/ExportTagsCommand.cs +++ b/AppInspector/Commands/ExportTagsCommand.cs @@ -9,7 +9,7 @@ using Microsoft.ApplicationInspector.RulesEngine; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.Commands; @@ -39,13 +39,13 @@ public ExportTagsResult() TagsList = new List(); } - [JsonProperty(Order = 2, PropertyName = "resultCode")] + [JsonPropertyName("resultCode")] public ExitCode ResultCode { get; set; } /// /// List of tags exported from specified ruleset /// - [JsonProperty(Order = 3, PropertyName = "tagsList")] + [JsonPropertyName("tagsList")] public List TagsList { get; set; } } diff --git a/AppInspector/Commands/PackRulesCommand.cs b/AppInspector/Commands/PackRulesCommand.cs index 5a19711c..effc0be6 100644 --- a/AppInspector/Commands/PackRulesCommand.cs +++ b/AppInspector/Commands/PackRulesCommand.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using Microsoft.ApplicationInspector.Common; using Microsoft.ApplicationInspector.RulesEngine; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; namespace Microsoft.ApplicationInspector.Commands; @@ -31,12 +31,13 @@ public enum ExitCode CriticalError = Utils.ExitCode.CriticalError //ensure common value for final exit log mention } - [JsonProperty(Order = 2)] public ExitCode ResultCode { get; set; } + [JsonPropertyName("resultCode")] // Order 2 + public ExitCode ResultCode { get; set; } /// /// List of Rules to pack as specified in pack command /// - [JsonProperty(Order = 3)] + [JsonPropertyName("rules")] // Order 3 public List? Rules { get; set; } } diff --git a/AppInspector/Commands/TagDiffCommand.cs b/AppInspector/Commands/TagDiffCommand.cs index 42719788..8ac67495 100644 --- a/AppInspector/Commands/TagDiffCommand.cs +++ b/AppInspector/Commands/TagDiffCommand.cs @@ -8,7 +8,7 @@ using Microsoft.ApplicationInspector.RulesEngine; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.Commands; @@ -58,13 +58,13 @@ public enum DiffSource /// /// Tag value from rule used in comparison /// - [JsonProperty(PropertyName = "tag")] + [JsonPropertyName("tag")] public string? Tag { get; set; } /// /// Source file (src1/src2) from the command option arguments /// - [JsonProperty(PropertyName = "source")] + [JsonPropertyName("source")] public DiffSource Source { get; set; } } @@ -83,7 +83,7 @@ public enum ExitCode /// /// List of tags which differ between src1 and src2 /// - [JsonProperty(Order = 3, PropertyName = "tagDiffList")] + [JsonPropertyName("tagDiffList")] public List TagDiffList; public TagDiffResult() @@ -91,7 +91,7 @@ public TagDiffResult() TagDiffList = new List(); } - [JsonProperty(Order = 2, PropertyName = "resultCode")] + [JsonPropertyName("resultCode")] // Order 2 public ExitCode ResultCode { get; set; } } diff --git a/AppInspector/Commands/VerifyRulesCommand.cs b/AppInspector/Commands/VerifyRulesCommand.cs index 7e48b321..f3a0a958 100644 --- a/AppInspector/Commands/VerifyRulesCommand.cs +++ b/AppInspector/Commands/VerifyRulesCommand.cs @@ -9,8 +9,8 @@ using Microsoft.ApplicationInspector.RulesEngine.OatExtensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; +using System.Text.Json; namespace Microsoft.ApplicationInspector.Commands; @@ -27,7 +27,7 @@ public class VerifyRulesOptions public class VerifyRulesResult : Result { - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum ExitCode { Verified = 0, @@ -40,10 +40,10 @@ public VerifyRulesResult() RuleStatusList = new List(); } - [JsonProperty(PropertyName = "resultCode")] + [JsonPropertyName("resultCode")] public ExitCode ResultCode { get; set; } - [JsonProperty(PropertyName = "ruleStatusList")] + [JsonPropertyName("ruleStatusList")] public List RuleStatusList { get; set; } [JsonIgnore] public IEnumerable Unverified => RuleStatusList.Where(x => !x.Verified); diff --git a/AppInspector/FileRecord.cs b/AppInspector/FileRecord.cs index a17ee908..50f9f5cb 100644 --- a/AppInspector/FileRecord.cs +++ b/AppInspector/FileRecord.cs @@ -1,6 +1,5 @@ using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.Commands; @@ -15,7 +14,7 @@ public class FileRecord public DateTime AccessTime { get; set; } = DateTime.MinValue; } -[JsonConverter(typeof(StringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum ScanState { None, diff --git a/AppInspector/MetaData.cs b/AppInspector/MetaData.cs index 2fb9c74e..c5201a9b 100644 --- a/AppInspector/MetaData.cs +++ b/AppInspector/MetaData.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.ApplicationInspector.RulesEngine; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.Commands; @@ -24,37 +24,37 @@ public MetaData(string applicationName, string sourcePath) /// /// Detected or derived project name /// - [JsonProperty(PropertyName = "applicationName")] + [JsonPropertyName("applicationName")] public string? ApplicationName { get; set; } /// /// Source path provided argument /// - [JsonProperty(PropertyName = "sourcePath")] + [JsonPropertyName("sourcePath")] public string? SourcePath { get; set; } /// /// Detected project source version /// - [JsonProperty(PropertyName = "sourceVersion")] + [JsonPropertyName("sourceVersion")] public string? SourceVersion { get; set; } /// /// Detected source authors /// - [JsonProperty(PropertyName = "authors")] + [JsonPropertyName("authors")] public string? Authors { get; set; } /// /// Detected source description /// - [JsonProperty(PropertyName = "description")] + [JsonPropertyName("description")] public string? Description { get; set; } /// /// Last modified date for source code scanned /// - [JsonProperty(PropertyName = "lastUpdated")] + [JsonPropertyName("lastUpdated")] public string LastUpdated { get @@ -71,27 +71,27 @@ public string LastUpdated /// /// Date of analyze scan /// - [JsonProperty(PropertyName = "dateScanned")] + [JsonPropertyName("dateScanned")] public string? DateScanned { get; set; } /// /// True if the overall analysis timed out /// - [JsonProperty(PropertyName = "timedOut")] + [JsonPropertyName("timedOut")] public bool TimedOut { get; set; } //stats /// /// Total number of files in source path /// - [JsonProperty(PropertyName = "totalFiles")] + [JsonPropertyName("totalFiles")] public int TotalFiles => Files.Count; /// /// Total number of files Timed out on an individual timeout /// - [JsonProperty(PropertyName = "filesTimedOut")] + [JsonPropertyName("filesTimedOut")] public int FilesTimedOut { get { return Files.Count(x => x.Status == ScanState.TimedOut); } @@ -100,7 +100,7 @@ public int FilesTimedOut /// /// Total number of files scanned /// - [JsonProperty(PropertyName = "filesAnalyzed")] + [JsonPropertyName("filesAnalyzed")] public int FilesAnalyzed { get { return Files.Count(x => x.Status is ScanState.Analyzed or ScanState.Affected); } @@ -109,7 +109,7 @@ public int FilesAnalyzed /// /// Total number of skipped files based on supported formats /// - [JsonProperty(PropertyName = "filesSkipped")] + [JsonPropertyName("filesSkipped")] public int FilesSkipped { get { return Files.Count(x => x.Status == ScanState.Skipped); } @@ -118,7 +118,7 @@ public int FilesSkipped /// /// Total number of skipped files based on overall timeout /// - [JsonProperty(PropertyName = "filesTimeOutSkipped")] + [JsonPropertyName("filesTimeOutSkipped")] public int FilesTimeOutSkipped { get { return Files.Count(x => x.Status == ScanState.TimeOutSkipped); } @@ -127,7 +127,7 @@ public int FilesTimeOutSkipped /// /// Total files with at least one result /// - [JsonProperty(PropertyName = "filesAffected")] + [JsonPropertyName("filesAffected")] public int FilesAffected { get { return Files.Count(x => x.Status == ScanState.Affected); } @@ -136,7 +136,7 @@ public int FilesAffected /// /// Number of files which encountered an error when processing other than timing out. /// - [JsonProperty(PropertyName = "filesErrored")] + [JsonPropertyName("filesErrored")] public int FileErrored { get { return Files.Count(x => x.Status == ScanState.Error); } @@ -145,13 +145,13 @@ public int FileErrored /// /// Total matches with supplied argument settings /// - [JsonProperty(PropertyName = "totalMatchesCount")] + [JsonPropertyName("totalMatchesCount")] public int TotalMatchesCount => Matches?.Count ?? 0; /// /// Total unique matches by Rule Id /// - [JsonProperty(PropertyName = "uniqueMatchesCount")] + [JsonPropertyName("uniqueMatchesCount")] public int UniqueMatchesCount { get { return Matches?.Select(x => x.RuleId).Distinct().Count() ?? 0; } @@ -160,81 +160,81 @@ public int UniqueMatchesCount /// /// List of detected package types /// - [JsonProperty(PropertyName = "packageTypes")] + [JsonPropertyName("packageTypes")] public List? PackageTypes { get; set; } = new(); /// /// List of detected application types /// - [JsonProperty(PropertyName = "appTypes")] + [JsonPropertyName("appTypes")] public List? AppTypes { get; set; } = new(); /// /// List of detected unique tags /// - [JsonProperty(PropertyName = "uniqueTags")] + [JsonPropertyName("uniqueTags")] public List UniqueTags { get; set; } = new(); /// /// List of detected unique code dependency includes /// - [JsonProperty(PropertyName = "uniqueDependencies")] + [JsonPropertyName("uniqueDependencies")] public List? UniqueDependencies { get; set; } = new(); /// /// List of detected output types /// - [JsonProperty(PropertyName = "outputs")] + [JsonPropertyName("outputs")] public List? Outputs { get; set; } = new(); /// /// List of detected target types /// - [JsonProperty(PropertyName = "targets")] + [JsonPropertyName("targets")] public List Targets { get; set; } = new(); /// /// List of detected OS targets /// - [JsonProperty(PropertyName = "OSTargets")] + [JsonPropertyName("OSTargets")] public List? OSTargets { get; set; } = new(); /// /// LIst of detected file types (extension based) /// - [JsonProperty(PropertyName = "fileExtensions")] + [JsonPropertyName("fileExtensions")] public List? FileExtensions { get; set; } = new(); /// /// List of detected cloud host targets /// - [JsonProperty(PropertyName = "cloudTargets")] + [JsonPropertyName("cloudTargets")] public List? CloudTargets { get; set; } = new(); /// /// List of detected cpu targets /// - [JsonProperty(PropertyName = "CPUTargets")] + [JsonPropertyName("CPUTargets")] public List? CPUTargets { get; set; } = new(); /// /// List of detected programming languages used and count of files /// - [JsonProperty(PropertyName = "languages")] + [JsonPropertyName("languages")] public IDictionary? Languages { get; set; } //unable to init here for constr arg /// /// List of detected tag counters i.e. metrics /// - [JsonProperty(PropertyName = "tagCounters")] + [JsonPropertyName("tagCounters")] public List? TagCounters { get; set; } = new(); /// /// List of detailed MatchRecords from scan /// - [JsonProperty(PropertyName = "detailedMatchList")] + [JsonPropertyName("detailedMatchList")] public List Matches { get; set; } = new(); - [JsonProperty(PropertyName = "filesInformation")] + [JsonPropertyName("filesInformation")] public List Files { get; set; } = new(); } \ No newline at end of file diff --git a/AppInspector/MetaDataHelper.cs b/AppInspector/MetaDataHelper.cs index 19ef1bf7..a72b3d5f 100644 --- a/AppInspector/MetaDataHelper.cs +++ b/AppInspector/MetaDataHelper.cs @@ -6,7 +6,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using Microsoft.ApplicationInspector.RulesEngine; +[assembly:InternalsVisibleTo("AppInspector.Benchmarks")] namespace Microsoft.ApplicationInspector.Commands; @@ -151,7 +153,6 @@ public void AddMatchRecord(MatchRecord matchRecord) } } - /// /// Transfer concurrent data from scan to analyze result with sorted, simplier types for callers /// diff --git a/AppInspector/MetricTagCounter.cs b/AppInspector/MetricTagCounter.cs index 0454ca8a..d420a132 100644 --- a/AppInspector/MetricTagCounter.cs +++ b/AppInspector/MetricTagCounter.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Text.Json.Serialization; using System.Threading; -using Newtonsoft.Json; namespace Microsoft.ApplicationInspector.Commands; @@ -14,9 +14,11 @@ public class MetricTagCounter { private int _count; - [JsonProperty(PropertyName = "tag")] public string? Tag { get; set; } + [JsonPropertyName("tag")] + public string? Tag { get; set; } - [JsonProperty(PropertyName = "count")] public int Count => _count; + [JsonPropertyName("count")] + public int Count => _count; internal void IncrementCount(int amount = 1) { diff --git a/AppInspector/Result.cs b/AppInspector/Result.cs index 9fce18c2..13c037e9 100644 --- a/AppInspector/Result.cs +++ b/AppInspector/Result.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Microsoft.ApplicationInspector.Commands; @@ -10,6 +10,6 @@ namespace Microsoft.ApplicationInspector.Commands; /// public class Result { - [JsonProperty(Order = 1, PropertyName = "appVersion")] + [JsonPropertyName("appVersion")] // Order 1 public string? AppVersion { get; set; } } \ No newline at end of file diff --git a/version.json b/version.json index e2f55d4e..5b110db6 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.6", + "version": "1.7", "publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/heads/v\\d+(?:\\.\\d+)?$"