diff --git a/src/ESLint.Formatter/sarif.js b/src/ESLint.Formatter/sarif.js index ddfe98149..b313d37dd 100644 --- a/src/ESLint.Formatter/sarif.js +++ b/src/ESLint.Formatter/sarif.js @@ -132,9 +132,11 @@ module.exports = function (results, data) { console.log(err); } } - if (result.messages.length > 0) { - result.messages.forEach(message => { + const messages = result.suppressedMessages ? result.messages.concat(result.suppressedMessages) : result.messages; + + if (messages.length > 0) { + messages.forEach(message => { const sarifRepresentation = { level: getResultLevel(message), @@ -208,7 +210,7 @@ module.exports = function (results, data) { } if (message.column > 0) { sarifRepresentation.locations[0].physicalLocation.region.startColumn = message.column; - }; + } } if (message.source) { @@ -220,6 +222,17 @@ module.exports = function (results, data) { }; } + if (message.suppressions) { + sarifRepresentation.suppressions = message.suppressions.map(suppression => { + return { + kind: suppression.kind === "directive" ? "inSource" : "external", + justification: suppression.justification + } + }); + } else { + sarifRepresentation.suppressions = []; + } + if (message.ruleId) { sarifResults.push(sarifRepresentation); } else { diff --git a/src/ESLint.Formatter/test/sarif.js b/src/ESLint.Formatter/test/sarif.js index 666e69c88..fb9bb10c7 100644 --- a/src/ESLint.Formatter/test/sarif.js +++ b/src/ESLint.Formatter/test/sarif.js @@ -87,7 +87,8 @@ describe("formatter:sarif", () => { describe("when passed no messages", () => { const code = [{ filePath: sourceFilePath1, - messages: [] + messages: [], + suppressedMessages: [] }]; it("should return a log with files, but no results", () => { @@ -103,6 +104,94 @@ describe("formatter:sarif", () => { describe("formatter:sarif", () => { describe("when passed one message", () => { + const code = [{ + filePath: sourceFilePath1, + messages: [{ + message: "Unexpected value.", + severity: 2, + ruleId: testRuleId + }], + suppressedMessages: [] + }]; + + it("should return a log with one file and one result", () => { + const log = JSON.parse(formatter(code, { rulesMeta: rules })); + + assert(log.runs[0].artifacts[0].location.uri.startsWith(uriPrefix)); + assert(log.runs[0].artifacts[0].location.uri, "/" + sourceFilePath1); + assert.isDefined(log.runs[0].results); + assert.lengthOf(log.runs[0].results, 1); + assert.strictEqual(log.runs[0].results[0].level, "error"); + assert.isDefined(log.runs[0].results[0].message); + assert.strictEqual(log.runs[0].results[0].message.text, code[0].messages[0].message); + assert(log.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri.startsWith(uriPrefix)); + assert(log.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri.endsWith("/" + sourceFilePath1)); + assert.strictEqual(log.runs[0].results[0].locations[0].physicalLocation.artifactLocation.index, 0); + assert.isDefined(log.runs[0].results[0].suppressions); + assert.lengthOf(log.runs[0].results[0].suppressions, 0); + }); + }); + + describe("when passed one suppressedMessage", () => { + const code = [{ + filePath: sourceFilePath1, + messages: [], + suppressedMessages: [{ + message: "Unexpected value.", + severity: 2, + ruleId: testRuleId, + suppressions: [{ kind: "directive", justification: "foo" }] + }] + }]; + + it("should return a log with one file and one result", () => { + const log = JSON.parse(formatter(code, { rulesMeta: rules })); + + assert(log.runs[0].artifacts[0].location.uri.startsWith(uriPrefix)); + assert(log.runs[0].artifacts[0].location.uri, "/" + sourceFilePath1); + assert.isDefined(log.runs[0].results); + assert.lengthOf(log.runs[0].results, 1); + assert.strictEqual(log.runs[0].results[0].level, "error"); + assert.isDefined(log.runs[0].results[0].message); + assert.strictEqual(log.runs[0].results[0].message.text, code[0].suppressedMessages[0].message); + assert(log.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri.startsWith(uriPrefix)); + assert(log.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri.endsWith("/" + sourceFilePath1)); + assert.strictEqual(log.runs[0].results[0].locations[0].physicalLocation.artifactLocation.index, 0); + assert.lengthOf(log.runs[0].results[0].suppressions, 1); + assert.strictEqual(log.runs[0].results[0].suppressions[0].kind, "inSource"); + assert.strictEqual(log.runs[0].results[0].suppressions[0].justification, code[0].suppressedMessages[0].suppressions[0].justification); + }); + }); + + describe("when passed one suppressedMessage with multiple suppressions", () => { + const code = [{ + filePath: sourceFilePath1, + messages: [], + suppressedMessages: [{ + message: "Unexpected value.", + severity: 2, + ruleId: testRuleId, + suppressions: [ + { kind: "directive", justification: "foo" }, + { kind: "directive", justification: "bar" } + ] + }] + }]; + + it("should return a log with one file and one result", () => { + const log = JSON.parse(formatter(code, { rulesMeta: rules })); + + assert.isDefined(log.runs[0].results[0].message); + assert.strictEqual(log.runs[0].results[0].message.text, code[0].suppressedMessages[0].message); + assert.lengthOf(log.runs[0].results[0].suppressions, 2); + assert.strictEqual(log.runs[0].results[0].suppressions[0].kind, "inSource"); + assert.strictEqual(log.runs[0].results[0].suppressions[0].justification, code[0].suppressedMessages[0].suppressions[0].justification); + assert.strictEqual(log.runs[0].results[0].suppressions[1].kind, "inSource"); + assert.strictEqual(log.runs[0].results[0].suppressions[1].justification, code[0].suppressedMessages[0].suppressions[1].justification); + }); + }); + + describe("when passed one message and no suppressedMessages array", () => { const code = [{ filePath: sourceFilePath1, messages: [{ @@ -114,7 +203,7 @@ describe("formatter:sarif", () => { it("should return a log with one file and one result", () => { const log = JSON.parse(formatter(code, { rulesMeta: rules })); - + assert(log.runs[0].artifacts[0].location.uri.startsWith(uriPrefix)); assert(log.runs[0].artifacts[0].location.uri, "/" + sourceFilePath1); assert.isDefined(log.runs[0].results); @@ -125,6 +214,8 @@ describe("formatter:sarif", () => { assert(log.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri.startsWith(uriPrefix)); assert(log.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri.endsWith("/" + sourceFilePath1)); assert.strictEqual(log.runs[0].results[0].locations[0].physicalLocation.artifactLocation.index, 0); + assert.isDefined(log.runs[0].results[0].suppressions); + assert.lengthOf(log.runs[0].results[0].suppressions, 0); }); }); }); @@ -138,7 +229,8 @@ describe("formatter:sarif", () => { message: "Unexpected value.", ruleId: ruleid, source: "getValue()" - }] + }], + suppressedMessages: [] }]; it("should return a log with one rule", () => { @@ -162,7 +254,8 @@ describe("formatter:sarif", () => { message: "Unexpected value.", ruleId: testRuleId, line: 10 - }] + }], + suppressedMessages: [] }]; it("should return a log with one result whose location region has only a startLine", () => { @@ -184,7 +277,8 @@ describe("formatter:sarif", () => { ruleId: testRuleId, line: 10, column: 0 - }] + }], + suppressedMessages: [] }]; it("should return a log with one result whose location contains a region with line # and no column #", () => { @@ -206,7 +300,8 @@ describe("formatter:sarif", () => { ruleId: testRuleId, line: 10, column: 5 - }] + }], + suppressedMessages: [] }]; it("should return a log with one result whose location contains a region with line and column #s", () => { @@ -229,7 +324,8 @@ describe("formatter:sarif", () => { line: 10, column: 5, source: "getValue()" - }] + }], + suppressedMessages: [] }]; it("should return a log with one result whose location contains a region with line and column #s", () => { @@ -251,7 +347,8 @@ describe("formatter:sarif", () => { severity: 2, ruleId: testRuleId, source: "getValue()" - }] + }], + suppressedMessages: [] }]; it("should return a log with one result whose location contains a region with line and column #s", () => { @@ -264,6 +361,39 @@ describe("formatter:sarif", () => { }); }); + + +describe("formatter:sarif", () => { + describe("when passed one message and one suppressedMessage", () => { + const code = [{ + filePath: sourceFilePath1, + messages: [{ + message: "Unexpected value.", + severity: 2, + ruleId: testRuleId, + source: "getValue()" + }], + suppressedMessages: [{ + message: "Unexpected value.", + severity: 2, + ruleId: testRuleId, + source: "getValue()", + suppressions: [{ kind: "directive", justification: "foo" }] + }] + }]; + + it("should return a log with two results, one of which has suppressions", () => { + const log = JSON.parse(formatter(code, rules)); + + assert.lengthOf(log.runs[0].results, 2) + assert.lengthOf(log.runs[0].results[0].suppressions, 0); + assert.lengthOf(log.runs[0].results[1].suppressions, 1); + assert.strictEqual(log.runs[0].results[1].suppressions[0].kind, "inSource"); + assert.strictEqual(log.runs[0].results[1].suppressions[0].justification, code[0].suppressedMessages[0].suppressions[0].justification); + }); + }); +}); + describe("formatter:sarif", () => { describe("when passed two results with two messages each", () => { const ruleid1 = "no-unused-vars"; @@ -291,7 +421,8 @@ describe("formatter:sarif", () => { line: 10, column: 5, source: "doSomething(thingId)" - }] + }], + suppressedMessages: [] }, { filePath: sourceFilePath2, @@ -305,7 +436,8 @@ describe("formatter:sarif", () => { message: "Custom error.", ruleId: ruleid3, line: 42 - }] + }], + suppressedMessages: [] }]; it("should return a log with two files, three rules, and four results", () => { @@ -385,6 +517,11 @@ describe("formatter:sarif", () => { assert.strictEqual(log.runs[0].results[2].locations[0].physicalLocation.region.startLine, 18); assert.isUndefined(log.runs[0].results[2].locations[0].physicalLocation.region.startColumn); assert.isUndefined(log.runs[0].results[2].locations[0].physicalLocation.region.snippet); + + assert.lengthOf(log.runs[0].results[0].suppressions, 0); + assert.lengthOf(log.runs[0].results[1].suppressions, 0); + assert.lengthOf(log.runs[0].results[2].suppressions, 0); + assert.lengthOf(log.runs[0].results[3].suppressions, 0); }); }); }); @@ -404,7 +541,8 @@ describe("formatter:sarif", () => { }; const code = [{ filePath: sourceFilePath1, - messages: [ ] + messages: [], + suppressedMessages: [] }, { filePath: sourceFilePath2, @@ -418,7 +556,8 @@ describe("formatter:sarif", () => { message: "Custom error.", ruleId: ruleid3, line: 42 - }] + }], + suppressedMessages: [] }]; it("should return a log with two files, two rules, and two results", () => { @@ -464,6 +603,9 @@ describe("formatter:sarif", () => { assert.strictEqual(log.runs[0].results[0].locations[0].physicalLocation.region.startLine, 18); assert.isUndefined(log.runs[0].results[0].locations[0].physicalLocation.region.startColumn); assert.isUndefined(log.runs[0].results[0].locations[0].physicalLocation.region.snippet); + + assert.lengthOf(log.runs[0].results[0].suppressions, 0); + assert.lengthOf(log.runs[0].results[1].suppressions, 0); }); }); }); @@ -476,7 +618,8 @@ describe("formatter:sarif", () => { message: "Internal error.", severity: 2, // no ruleId property - }] + }], + suppressedMessages: [] }]; it("should return a log with no rules, no results, and a tool execution notification", () => { @@ -526,7 +669,8 @@ describe("formatter:sarif", () => { message: "Custom error.", ruleId: ruleid, line: 42 - }] + }], + suppressedMessages: [] }]; it("should return a log with one file, one rule, and one result", () => { const log = JSON.parse(formatter(code, { rulesMeta: rules })); diff --git a/src/ReleaseHistory.md b/src/ReleaseHistory.md index 3441bbe16..1811a5a09 100644 --- a/src/ReleaseHistory.md +++ b/src/ReleaseHistory.md @@ -7,6 +7,7 @@ * BREAKING: Fix `InvalidOperationException` when using PropertiesDictionary in a multithreaded application, and remove `[Serializable]` from it. Now use of BinaryFormatter on it will result in `SerializationException`: Type `PropertiesDictionary` is not marked as serializable. [#2415](https://github.com/microsoft/sarif-sdk/pull/2415) * BREAKING: `SarifLogger` now emits an artifacts table entry if `artifactLocation` is not null for tool configuration and tool execution notifications. [#2437](https://github.com/microsoft/sarif-sdk/pull/2437) * BUGFIX: Fix `ArgumentException` when `--recurse` is enabled and two file target specifiers generates the same file path. [#2438](https://github.com/microsoft/sarif-sdk/pull/2438) +* FEATURE: Add `--sort-results` argument to `rewrite` command to get sorted SARIF results. [#2422](https://github.com/microsoft/sarif-sdk/pull/2422) ## **v2.4.12** [Sdk](https://www.nuget.org/packages/Sarif.Sdk/2.4.12) | [Driver](https://www.nuget.org/packages/Sarif.Driver/2.4.12) | [Converters](https://www.nuget.org/packages/Sarif.Converters/2.4.12) | [Multitool](https://www.nuget.org/packages/Sarif.Multitool/2.4.12) | [Multitool Library](https://www.nuget.org/packages/Sarif.Multitool.Library/2.4.12) diff --git a/src/Sarif/Comparers/ArtifactSortingComparer.cs b/src/Sarif/Comparers/ArtifactComparer.cs similarity index 51% rename from src/Sarif/Comparers/ArtifactSortingComparer.cs rename to src/Sarif/Comparers/ArtifactComparer.cs index a4db5c069..36fdd5c68 100644 --- a/src/Sarif/Comparers/ArtifactSortingComparer.cs +++ b/src/Sarif/Comparers/ArtifactComparer.cs @@ -2,137 +2,110 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; -using System.Linq; /// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. /// Tracking by issue: https://github.com/microsoft/jschema/issues/141 /// -namespace Microsoft.CodeAnalysis.Sarif +namespace Microsoft.CodeAnalysis.Sarif.Comparers { - internal class ArtifactSortingComparer : IComparer + internal class ArtifactComparer : IComparer { - internal static readonly ArtifactSortingComparer Instance = new ArtifactSortingComparer(); + internal static readonly ArtifactComparer Instance = new ArtifactComparer(); public int Compare(Artifact left, Artifact right) { - if (ReferenceEquals(left, right)) + if (ComparerHelper.CompareReference(left, right, out int compareResult)) { - return 0; - } - - if (left == null) - { - return -1; + return compareResult; } - if (right == null) - { - return 1; - } + compareResult = MessageComparer.Instance.Compare(left.Description, right.Description); - int compareResult = 0; - compareResult = MessageSortingComparer.Instance.Compare(left.Description, right.Description); if (compareResult != 0) { return compareResult; } - compareResult = ArtifactLocationSortingComparer.Instance.Compare(left.Location, right.Location); + compareResult = ArtifactLocationComparer.Instance.Compare(left.Location, right.Location); + if (compareResult != 0) { return compareResult; } compareResult = left.ParentIndex.CompareTo(right.ParentIndex); + if (compareResult != 0) { return compareResult; } compareResult = left.Offset.CompareTo(right.Offset); + if (compareResult != 0) { return compareResult; } compareResult = left.Length.CompareTo(right.Length); + if (compareResult != 0) { return compareResult; } compareResult = left.Roles.CompareTo(right.Roles); + if (compareResult != 0) { return compareResult; } compareResult = string.Compare(left.MimeType, right.MimeType); + if (compareResult != 0) { return compareResult; } - compareResult = ArtifactContentSortingComparer.Instance.Compare(left.Contents, right.Contents); + compareResult = ArtifactContentComparer.Instance.Compare(left.Contents, right.Contents); + if (compareResult != 0) { return compareResult; } compareResult = string.Compare(left.Encoding, right.Encoding); + if (compareResult != 0) { return compareResult; } compareResult = string.Compare(left.SourceLanguage, right.SourceLanguage); + if (compareResult != 0) { return compareResult; } - if (!object.ReferenceEquals(left.Hashes, right.Hashes)) + compareResult = ComparerHelper.CompareDictionary(left.Hashes, right.Hashes); + + if (compareResult != 0) { - if (left.Hashes == null) - { - return -1; - } - - if (right.Hashes == null) - { - return 1; - } - - compareResult = left.Hashes.Count.CompareTo(right.Hashes.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int i = 0; i < left.Hashes.Count; i++) - { - compareResult = string.Compare(left.Hashes.ElementAt(i).Key, right.Hashes.ElementAt(i).Key); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = string.Compare(left.Hashes.ElementAt(i).Value, right.Hashes.ElementAt(i).Value); - if (compareResult != 0) - { - return compareResult; - } - } + return compareResult; } compareResult = left.LastModifiedTimeUtc.CompareTo(right.LastModifiedTimeUtc); + if (compareResult != 0) { return compareResult; } + // Warning there may be properties are not compared. return compareResult; } } diff --git a/src/Sarif/Comparers/ArtifactContentSortingComparer.cs b/src/Sarif/Comparers/ArtifactContentComparer.cs similarity index 53% rename from src/Sarif/Comparers/ArtifactContentSortingComparer.cs rename to src/Sarif/Comparers/ArtifactContentComparer.cs index e0c0d2896..1a1709a4e 100644 --- a/src/Sarif/Comparers/ArtifactContentSortingComparer.cs +++ b/src/Sarif/Comparers/ArtifactContentComparer.cs @@ -4,52 +4,45 @@ using System.Collections.Generic; /// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. /// Tracking by issue: https://github.com/microsoft/jschema/issues/141 /// -namespace Microsoft.CodeAnalysis.Sarif +namespace Microsoft.CodeAnalysis.Sarif.Comparers { - internal class ArtifactContentSortingComparer : IComparer + internal class ArtifactContentComparer : IComparer { - internal static readonly ArtifactContentSortingComparer Instance = new ArtifactContentSortingComparer(); + internal static readonly ArtifactContentComparer Instance = new ArtifactContentComparer(); public int Compare(ArtifactContent left, ArtifactContent right) { - if (ReferenceEquals(left, right)) + if (ComparerHelper.CompareReference(left, right, out int compareResult)) { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; + return compareResult; } - int compareResult = 0; compareResult = string.Compare(left.Text, right.Text); + if (compareResult != 0) { return compareResult; } compareResult = string.Compare(left.Binary, right.Binary); + if (compareResult != 0) { return compareResult; } - compareResult = MultiformatMessageStringSortingComparer.Instance.Compare(left.Rendered, right.Rendered); + compareResult = MultiformatMessageStringComparer.Instance.Compare(left.Rendered, right.Rendered); + if (compareResult != 0) { return compareResult; } + // Warning there may be properties are not compared. return compareResult; } } diff --git a/src/Sarif/Comparers/ArtifactLocationComparer.cs b/src/Sarif/Comparers/ArtifactLocationComparer.cs new file mode 100644 index 000000000..9751aa98f --- /dev/null +++ b/src/Sarif/Comparers/ArtifactLocationComparer.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +/// +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. +/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 +/// +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class ArtifactLocationComparer : IComparer + { + internal static readonly ArtifactLocationComparer Instance = new ArtifactLocationComparer(); + + public int Compare(ArtifactLocation left, ArtifactLocation right) + { + if (ComparerHelper.CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareUri(left.Uri, right.Uri); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.UriBaseId, right.UriBaseId); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = left.Index.CompareTo(right.Index); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = MessageComparer.Instance.Compare(left.Description, right.Description); + + if (compareResult != 0) + { + return compareResult; + } + + // Warning there may be properties are not compared. + return compareResult; + } + } +} diff --git a/src/Sarif/Comparers/ArtifactLocationSortingComparer.cs b/src/Sarif/Comparers/ArtifactLocationSortingComparer.cs deleted file mode 100644 index aac3fa1be..000000000 --- a/src/Sarif/Comparers/ArtifactLocationSortingComparer.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -/// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. -/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 -/// -namespace Microsoft.CodeAnalysis.Sarif -{ - internal class ArtifactLocationSortingComparer : IComparer - { - internal static readonly ArtifactLocationSortingComparer Instance = new ArtifactLocationSortingComparer(); - - public int Compare(ArtifactLocation left, ArtifactLocation right) - { - if (ReferenceEquals(left, right)) - { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; - } - - int compareResult = 0; - if (ReferenceEquals(left.Uri, right.Uri)) - { - return 0; - } - - if (left.Uri == null) - { - return -1; - } - - if (right.Uri == null) - { - return 1; - } - - compareResult = string.Compare(left.Uri.OriginalString, right.Uri.OriginalString); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = string.Compare(left.UriBaseId, right.UriBaseId); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = left.Index.CompareTo(right.Index); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = MessageSortingComparer.Instance.Compare(left.Description, right.Description); - if (compareResult != 0) - { - return compareResult; - } - - return compareResult; - } - } -} diff --git a/src/Sarif/Comparers/CodeFlowComparer.cs b/src/Sarif/Comparers/CodeFlowComparer.cs new file mode 100644 index 000000000..5823deacb --- /dev/null +++ b/src/Sarif/Comparers/CodeFlowComparer.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +/// +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. +/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 +/// +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class CodeFlowComparer : IComparer + { + internal static readonly CodeFlowComparer Instance = new CodeFlowComparer(); + + public int Compare(CodeFlow left, CodeFlow right) + { + if (ComparerHelper.CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = MessageComparer.Instance.Compare(left.Message, right.Message); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.ThreadFlows, right.ThreadFlows, ThreadFlowComparer.Instance); + + if (compareResult != 0) + { + return compareResult; + } + + // Warning there may be properties are not compared. + return compareResult; + } + } +} diff --git a/src/Sarif/Comparers/CodeFlowSortingComparer.cs b/src/Sarif/Comparers/CodeFlowSortingComparer.cs deleted file mode 100644 index d28f7b9d9..000000000 --- a/src/Sarif/Comparers/CodeFlowSortingComparer.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -/// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. -/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 -/// -namespace Microsoft.CodeAnalysis.Sarif -{ - internal class CodeFlowSortingComparer : IComparer - { - internal static readonly CodeFlowSortingComparer Instance = new CodeFlowSortingComparer(); - - public int Compare(CodeFlow left, CodeFlow right) - { - if (ReferenceEquals(left, right)) - { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; - } - - int compareResult = 0; - - compareResult = MessageSortingComparer.Instance.Compare(left.Message, right.Message); - if (compareResult != 0) - { - return compareResult; - } - - if (!object.ReferenceEquals(left.ThreadFlows, right.ThreadFlows)) - { - - if (left.ThreadFlows == null) - { - return -1; - } - - if (right.ThreadFlows == null) - { - return 1; - } - - compareResult = left.ThreadFlows.Count.CompareTo(right.ThreadFlows.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int index_1 = 0; index_1 < left.ThreadFlows.Count; ++index_1) - { - compareResult = ThreadFlowSortingComparer.Instance.Compare(left.ThreadFlows[index_1], right.ThreadFlows[index_1]); - if (compareResult != 0) - { - return compareResult; - } - } - } - - return compareResult; - } - } -} diff --git a/src/Sarif/Comparers/ComparerHelpers.cs b/src/Sarif/Comparers/ComparerHelpers.cs new file mode 100644 index 000000000..2b6300e27 --- /dev/null +++ b/src/Sarif/Comparers/ComparerHelpers.cs @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class ComparerHelper + { + /// + /// Compare 2 object refences. Return value false presents need to + /// compare objects values to get final result. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// 0 if both objects are same or both are null. + /// 1 if the first object is null and the second object is not null. + /// 2 if the first object is not null and the second object is null. + /// + /// Return true if can get a definite result, otherwise return false. + public static bool CompareReference(object left, object right, out int result) + { + result = 0; + + // ReferenceEquals returns true if both are null + if (object.ReferenceEquals(left, right)) + { + result = 0; + return true; + } + + if (left == null) + { + result = -1; + return true; + } + + if (right == null) + { + result = 1; + return true; + } + + // Cannot determine the comparison result based on reference + // need further comparing objects properties. + return false; + } + + /// + /// Compare 2 lists of type T derived from IComparable. + /// + /// type derived from IComparable + /// + /// + /// + public static int CompareList(IList left, IList right) where T : IComparable + { + return CompareListHelper(left, right, (a, b) => a.CompareTo(b)); + } + + /// + /// Compare 2 lists of type T using a Comparer. + /// + /// + /// + /// + /// + /// + /// + public static int CompareList(IList left, IList right, IComparer comparer) + { + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return CompareListHelper(left, right, comparer.Compare); + } + + /// Main function for comparing 2 lists. + private static int CompareListHelper(IList left, IList right, Func compareFunc) + { + if (compareFunc == null) + { + throw new ArgumentNullException(nameof(compareFunc)); + } + + if (CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = left.Count.CompareTo(right.Count); + + if (compareResult != 0) + { + return compareResult; + } + + for (int i = 0; i < left.Count; ++i) + { + if (CompareReference(left[i], right[i], out compareResult) && compareResult != 0) + { + return compareResult; + } + + compareResult = compareFunc(left[i], right[i]); + + if (compareResult != 0) + { + return compareResult; + } + } + + return compareResult; + } + + public static int CompareDictionary(IDictionary left, IDictionary right) where T : IComparable + { + return CompareDictionaryHelper(left, right, (a, b) => a.CompareTo(b)); + } + + public static int CompareDictionary(IDictionary left, IDictionary right, IComparer comparer) + { + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return CompareDictionaryHelper(left, right, comparer.Compare); + } + + /// Main function for comparing 2 dictionaries. + private static int CompareDictionaryHelper(IDictionary left, IDictionary right, Func compareFunc) + { + if (compareFunc == null) + { + throw new ArgumentNullException(nameof(compareFunc)); + } + + if (CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = left.Count.CompareTo(right.Count); + + if (compareResult != 0) + { + return compareResult; + } + + IList leftKeys = left.Keys.OrderBy(k => k).ToList(); + IList rightKeys = right.Keys.OrderBy(k => k).ToList(); + + for (int i = 0; i < leftKeys.Count; ++i) + { + compareResult = leftKeys[i].CompareTo(rightKeys[i]); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = compareFunc(left[leftKeys[i]], right[rightKeys[i]]); + + if (compareResult != 0) + { + return compareResult; + } + } + + return compareResult; + } + + /// + /// Compare 2 Uri objects using same parameters. + /// + /// + /// + /// + public static int CompareUri(Uri left, Uri right) + { + int result = Uri.Compare( + left, + right, + UriComponents.Path, + UriFormat.SafeUnescaped, + StringComparison.Ordinal); + + // Uri.Compare returns int value indicates lexical relationship between + // 2 Uris, can be any number. Just return 3 options 0/1/-1. + return result switch + { + var x when x == 0 => 0, + var x when x > 0 => 1, + var x when x < 0 => -1, + _ => result + }; + } + } +} diff --git a/src/Sarif/Comparers/LocationComparer.cs b/src/Sarif/Comparers/LocationComparer.cs new file mode 100644 index 000000000..893225c68 --- /dev/null +++ b/src/Sarif/Comparers/LocationComparer.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +/// +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. +/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 +/// +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class LocationComparer : IComparer + { + internal static readonly LocationComparer Instance = new LocationComparer(); + + public int Compare(Location left, Location right) + { + if (ComparerHelper.CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = left.Id.CompareTo(right.Id); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = PhysicalLocationComparer.Instance.Compare(left.PhysicalLocation, right.PhysicalLocation); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.LogicalLocations, right.LogicalLocations, LogicalLocationComparer.Instance); + + if (compareResult != 0) + { + return compareResult; + } + + // Warning there may be properties are not compared. + return compareResult; + } + } +} diff --git a/src/Sarif/Comparers/LocationSortingComparer.cs b/src/Sarif/Comparers/LocationSortingComparer.cs deleted file mode 100644 index de91361b8..000000000 --- a/src/Sarif/Comparers/LocationSortingComparer.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -/// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. -/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 -/// -namespace Microsoft.CodeAnalysis.Sarif -{ - internal class LocationSortingComparer : IComparer - { - internal static readonly LocationSortingComparer Instance = new LocationSortingComparer(); - - public int Compare(Location left, Location right) - { - if (ReferenceEquals(left, right)) - { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; - } - - int compareResult = 0; - compareResult = left.Id.CompareTo(right.Id); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = PhysicalLocationSortingComparer.Instance.Compare(left.PhysicalLocation, right.PhysicalLocation); - if (compareResult != 0) - { - return compareResult; - } - - if (!object.ReferenceEquals(left.LogicalLocations, right.LogicalLocations)) - { - if (left.LogicalLocations == null) - { - return -1; - } - - if (right.LogicalLocations == null) - { - return 1; - } - - compareResult = left.LogicalLocations.Count.CompareTo(right.LogicalLocations.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int i = 0; i < left.LogicalLocations.Count; ++i) - { - compareResult = LogicalLocationSortingComparer.Instance.Compare(left.LogicalLocations[i], right.LogicalLocations[i]); - if (compareResult != 0) - { - return compareResult; - } - } - } - - return compareResult; - } - } -} diff --git a/src/Sarif/Comparers/LogicalLocationSortingComparer.cs b/src/Sarif/Comparers/LogicalLocationComparer.cs similarity index 69% rename from src/Sarif/Comparers/LogicalLocationSortingComparer.cs rename to src/Sarif/Comparers/LogicalLocationComparer.cs index 59e295f82..9a7bab897 100644 --- a/src/Sarif/Comparers/LogicalLocationSortingComparer.cs +++ b/src/Sarif/Comparers/LogicalLocationComparer.cs @@ -4,70 +4,66 @@ using System.Collections.Generic; /// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. /// Tracking by issue: https://github.com/microsoft/jschema/issues/141 /// -namespace Microsoft.CodeAnalysis.Sarif +namespace Microsoft.CodeAnalysis.Sarif.Comparers { - internal class LogicalLocationSortingComparer : IComparer + internal class LogicalLocationComparer : IComparer { - internal static readonly LogicalLocationSortingComparer Instance = new LogicalLocationSortingComparer(); + internal static readonly LogicalLocationComparer Instance = new LogicalLocationComparer(); public int Compare(LogicalLocation left, LogicalLocation right) { - if (ReferenceEquals(left, right)) + if (ComparerHelper.CompareReference(left, right, out int compareResult)) { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; + return compareResult; } - int compareResult = 0; compareResult = string.Compare(left.Name, right.Name); + if (compareResult != 0) { return compareResult; } compareResult = left.Index.CompareTo(right.Index); + if (compareResult != 0) { return compareResult; } compareResult = string.Compare(left.FullyQualifiedName, right.FullyQualifiedName); + if (compareResult != 0) { return compareResult; } compareResult = string.Compare(left.DecoratedName, right.DecoratedName); + if (compareResult != 0) { return compareResult; } compareResult = left.ParentIndex.CompareTo(right.ParentIndex); + if (compareResult != 0) { return compareResult; } compareResult = string.Compare(left.Kind, right.Kind); + if (compareResult != 0) { return compareResult; } + // Warning there may be properties are not compared. return compareResult; } } diff --git a/src/Sarif/Comparers/MessageComparer.cs b/src/Sarif/Comparers/MessageComparer.cs new file mode 100644 index 000000000..de951435e --- /dev/null +++ b/src/Sarif/Comparers/MessageComparer.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +/// +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. +/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 +/// +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class MessageComparer : IComparer + { + internal static readonly MessageComparer Instance = new MessageComparer(); + + public int Compare(Message left, Message right) + { + if (ComparerHelper.CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = string.Compare(left.Text, right.Text); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.Markdown, right.Markdown); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.Id, right.Id); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.Arguments, right.Arguments); + + if (compareResult != 0) + { + return compareResult; + } + + // Warning there may be properties are not compared. + return compareResult; + } + } +} diff --git a/src/Sarif/Comparers/MessageSortingComparer.cs b/src/Sarif/Comparers/MessageSortingComparer.cs deleted file mode 100644 index 465621e7d..000000000 --- a/src/Sarif/Comparers/MessageSortingComparer.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -/// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. -/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 -/// -namespace Microsoft.CodeAnalysis.Sarif -{ - internal class MessageSortingComparer : IComparer - { - internal static readonly MessageSortingComparer Instance = new MessageSortingComparer(); - - public int Compare(Message left, Message right) - { - if (ReferenceEquals(left, right)) - { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; - } - - int compareResult = 0; - compareResult = string.Compare(left.Text, right.Text); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = string.Compare(left.Markdown, right.Markdown); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = string.Compare(left.Id, right.Id); - if (compareResult != 0) - { - return compareResult; - } - - if (!object.ReferenceEquals(left.Arguments, right.Arguments)) - { - if (left.Arguments == null) - { - return -1; - } - - if (right.Arguments == null) - { - return 1; - } - - compareResult = left.Arguments.Count.CompareTo(right.Arguments.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int i = 0; i < left.Arguments.Count; ++i) - { - compareResult = string.Compare(left.Arguments[i], right.Arguments[i]); - if (compareResult != 0) - { - return compareResult; - } - } - } - - return compareResult; - } - } -} diff --git a/src/Sarif/Comparers/MultiformatMessageStringSortingComparer.cs b/src/Sarif/Comparers/MultiformatMessageStringComparer.cs similarity index 54% rename from src/Sarif/Comparers/MultiformatMessageStringSortingComparer.cs rename to src/Sarif/Comparers/MultiformatMessageStringComparer.cs index 62f70413a..14306c2e4 100644 --- a/src/Sarif/Comparers/MultiformatMessageStringSortingComparer.cs +++ b/src/Sarif/Comparers/MultiformatMessageStringComparer.cs @@ -4,46 +4,38 @@ using System.Collections.Generic; /// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. /// Tracking by issue: https://github.com/microsoft/jschema/issues/141 /// -namespace Microsoft.CodeAnalysis.Sarif +namespace Microsoft.CodeAnalysis.Sarif.Comparers { - internal class MultiformatMessageStringSortingComparer : IComparer + internal class MultiformatMessageStringComparer : IComparer { - internal static readonly MultiformatMessageStringSortingComparer Instance = new MultiformatMessageStringSortingComparer(); + internal static readonly MultiformatMessageStringComparer Instance = new MultiformatMessageStringComparer(); public int Compare(MultiformatMessageString left, MultiformatMessageString right) { - if (ReferenceEquals(left, right)) + if (ComparerHelper.CompareReference(left, right, out int compareResult)) { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; + return compareResult; } - int compareResult = 0; compareResult = string.Compare(left.Text, right.Text); + if (compareResult != 0) { return compareResult; } compareResult = string.Compare(left.Markdown, right.Markdown); + if (compareResult != 0) { return compareResult; } + // Warning there may be properties are not compared. return compareResult; } } diff --git a/src/Sarif/Comparers/PhysicalLocationComparer.cs b/src/Sarif/Comparers/PhysicalLocationComparer.cs new file mode 100644 index 000000000..99694ec63 --- /dev/null +++ b/src/Sarif/Comparers/PhysicalLocationComparer.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +/// +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. +/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 +/// +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class PhysicalLocationComparer : IComparer + { + internal static readonly PhysicalLocationComparer Instance = new PhysicalLocationComparer(); + + public int Compare(PhysicalLocation left, PhysicalLocation right) + { + if (ComparerHelper.CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = ArtifactLocationComparer.Instance.Compare(left.ArtifactLocation, right.ArtifactLocation); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = RegionComparer.Instance.Compare(left.Region, right.Region); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = RegionComparer.Instance.Compare(left.ContextRegion, right.ContextRegion); + + if (compareResult != 0) + { + return compareResult; + } + + // Warning there may be properties are not compared. + return compareResult; + } + } +} diff --git a/src/Sarif/Comparers/PhysicalLocationSortingComparer.cs b/src/Sarif/Comparers/PhysicalLocationSortingComparer.cs deleted file mode 100644 index d1e8ac37a..000000000 --- a/src/Sarif/Comparers/PhysicalLocationSortingComparer.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -/// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. -/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 -/// -namespace Microsoft.CodeAnalysis.Sarif -{ - internal class PhysicalLocationSortingComparer : IComparer - { - internal static readonly PhysicalLocationSortingComparer Instance = new PhysicalLocationSortingComparer(); - - public int Compare(PhysicalLocation left, PhysicalLocation right) - { - if (ReferenceEquals(left, right)) - { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; - } - - int compareResult = 0; - - compareResult = ArtifactLocationSortingComparer.Instance.Compare(left.ArtifactLocation, right.ArtifactLocation); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = RegionSortingComparer.Instance.Compare(left.Region, right.Region); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = RegionSortingComparer.Instance.Compare(left.ContextRegion, right.ContextRegion); - if (compareResult != 0) - { - return compareResult; - } - - return compareResult; - } - } -} diff --git a/src/Sarif/Comparers/RegionSortingComparer.cs b/src/Sarif/Comparers/RegionComparer.cs similarity index 74% rename from src/Sarif/Comparers/RegionSortingComparer.cs rename to src/Sarif/Comparers/RegionComparer.cs index 57eb3b15b..94d522eba 100644 --- a/src/Sarif/Comparers/RegionSortingComparer.cs +++ b/src/Sarif/Comparers/RegionComparer.cs @@ -4,82 +4,80 @@ using System.Collections.Generic; /// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. /// Tracking by issue: https://github.com/microsoft/jschema/issues/141 /// -namespace Microsoft.CodeAnalysis.Sarif +namespace Microsoft.CodeAnalysis.Sarif.Comparers { - internal class RegionSortingComparer : IComparer + internal class RegionComparer : IComparer { - internal static readonly RegionSortingComparer Instance = new RegionSortingComparer(); + internal static readonly RegionComparer Instance = new RegionComparer(); public int Compare(Region left, Region right) { - if (ReferenceEquals(left, right)) + if (ComparerHelper.CompareReference(left, right, out int compareResult)) { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; + return compareResult; } - int compareResult = 0; compareResult = left.StartLine.CompareTo(right.StartLine); + if (compareResult != 0) { return compareResult; } compareResult = left.StartColumn.CompareTo(right.StartColumn); + if (compareResult != 0) { return compareResult; } compareResult = left.EndLine.CompareTo(right.EndLine); + if (compareResult != 0) { return compareResult; } compareResult = left.EndColumn.CompareTo(right.EndColumn); + if (compareResult != 0) { return compareResult; } compareResult = left.CharOffset.CompareTo(right.CharOffset); + if (compareResult != 0) { return compareResult; } compareResult = left.CharLength.CompareTo(right.CharLength); + if (compareResult != 0) { return compareResult; } compareResult = left.ByteOffset.CompareTo(right.ByteOffset); + if (compareResult != 0) { return compareResult; } compareResult = left.ByteLength.CompareTo(right.ByteLength); + if (compareResult != 0) { return compareResult; } + // Warning there may be properties are not compared. return compareResult; } } diff --git a/src/Sarif/Comparers/ReportingConfigurationSortingComparer.cs b/src/Sarif/Comparers/ReportingConfigurationComparer.cs similarity index 58% rename from src/Sarif/Comparers/ReportingConfigurationSortingComparer.cs rename to src/Sarif/Comparers/ReportingConfigurationComparer.cs index c8ca7452f..3ea985309 100644 --- a/src/Sarif/Comparers/ReportingConfigurationSortingComparer.cs +++ b/src/Sarif/Comparers/ReportingConfigurationComparer.cs @@ -4,52 +4,45 @@ using System.Collections.Generic; /// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. /// Tracking by issue: https://github.com/microsoft/jschema/issues/141 /// -namespace Microsoft.CodeAnalysis.Sarif +namespace Microsoft.CodeAnalysis.Sarif.Comparers { - internal class ReportingConfigurationSortingComparer : IComparer + internal class ReportingConfigurationComparer : IComparer { - internal static readonly ReportingConfigurationSortingComparer Instance = new ReportingConfigurationSortingComparer(); + internal static readonly ReportingConfigurationComparer Instance = new ReportingConfigurationComparer(); public int Compare(ReportingConfiguration left, ReportingConfiguration right) { - if (ReferenceEquals(left, right)) + if (ComparerHelper.CompareReference(left, right, out int compareResult)) { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; + return compareResult; } - int compareResult = 0; compareResult = left.Enabled.CompareTo(right.Enabled); + if (compareResult != 0) { return compareResult; } compareResult = left.Level.CompareTo(right.Level); + if (compareResult != 0) { return compareResult; } compareResult = left.Rank.CompareTo(right.Rank); + if (compareResult != 0) { return compareResult; } + // Warning there may be properties are not compared. return compareResult; } } diff --git a/src/Sarif/Comparers/ReportingDescriptorComparer.cs b/src/Sarif/Comparers/ReportingDescriptorComparer.cs new file mode 100644 index 000000000..c61f3adb1 --- /dev/null +++ b/src/Sarif/Comparers/ReportingDescriptorComparer.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +/// +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. +/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 +/// +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class ReportingDescriptorComparer : IComparer + { + internal static readonly ReportingDescriptorComparer Instance = new ReportingDescriptorComparer(); + + public int Compare(ReportingDescriptor left, ReportingDescriptor right) + { + if (ComparerHelper.CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = string.Compare(left.Id, right.Id); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.DeprecatedIds, right.DeprecatedIds); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.Guid, right.Guid); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.DeprecatedGuids, right.DeprecatedGuids); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.Name, right.Name); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.DeprecatedNames, right.DeprecatedNames); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = MultiformatMessageStringComparer.Instance.Compare(left.ShortDescription, right.ShortDescription); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = MultiformatMessageStringComparer.Instance.Compare(left.FullDescription, right.FullDescription); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ReportingConfigurationComparer.Instance.Compare(left.DefaultConfiguration, right.DefaultConfiguration); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareUri(left.HelpUri, right.HelpUri); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = MultiformatMessageStringComparer.Instance.Compare(left.Help, right.Help); + + if (compareResult != 0) + { + return compareResult; + } + + // Warning there may be properties are not compared. + return compareResult; + } + } +} diff --git a/src/Sarif/Comparers/ReportingDescriptorSortingComparer.cs b/src/Sarif/Comparers/ReportingDescriptorSortingComparer.cs deleted file mode 100644 index 3941229a4..000000000 --- a/src/Sarif/Comparers/ReportingDescriptorSortingComparer.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -/// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. -/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 -/// -namespace Microsoft.CodeAnalysis.Sarif -{ - internal class ReportingDescriptorSortingComparer : IComparer - { - internal static readonly ReportingDescriptorSortingComparer Instance = new ReportingDescriptorSortingComparer(); - - public int Compare(ReportingDescriptor left, ReportingDescriptor right) - { - if (ReferenceEquals(left, right)) - { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; - } - - int compareResult = 0; - compareResult = string.Compare(left.Id, right.Id); - if (compareResult != 0) - { - return compareResult; - } - - if (!object.ReferenceEquals(left.DeprecatedIds, right.DeprecatedIds)) - { - if (left.DeprecatedIds == null) - { - return -1; - } - - if (right.DeprecatedIds == null) - { - return 1; - } - - compareResult = left.DeprecatedIds.Count.CompareTo(right.DeprecatedIds.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int i = 0; i < left.DeprecatedIds.Count; ++i) - { - compareResult = string.Compare(left.DeprecatedIds[i], right.DeprecatedIds[i]); - if (compareResult != 0) - { - return compareResult; - } - } - } - - compareResult = string.Compare(left.Guid, right.Guid); - if (compareResult != 0) - { - return compareResult; - } - - if (!object.ReferenceEquals(left.DeprecatedGuids, right.DeprecatedGuids)) - { - if (left.DeprecatedGuids == null) - { - return -1; - } - - if (right.DeprecatedGuids == null) - { - return 1; - } - - compareResult = left.DeprecatedGuids.Count.CompareTo(right.DeprecatedGuids.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int i = 0; i < left.DeprecatedGuids.Count; ++i) - { - compareResult = string.Compare(left.DeprecatedGuids[i], right.DeprecatedGuids[i]); - if (compareResult != 0) - { - return compareResult; - } - } - } - - compareResult = string.Compare(left.Name, right.Name); - if (compareResult != 0) - { - return compareResult; - } - - if (!object.ReferenceEquals(left.DeprecatedNames, right.DeprecatedNames)) - { - if (left.DeprecatedNames == null) - { - return -1; - } - - if (right.DeprecatedNames == null) - { - return 1; - } - - compareResult = left.DeprecatedNames.Count.CompareTo(right.DeprecatedNames.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int i = 0; i < left.DeprecatedNames.Count; ++i) - { - compareResult = string.Compare(left.DeprecatedNames[i], right.DeprecatedNames[i]); - if (compareResult != 0) - { - return compareResult; - } - } - } - - compareResult = MultiformatMessageStringSortingComparer.Instance.Compare(left.ShortDescription, right.ShortDescription); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = MultiformatMessageStringSortingComparer.Instance.Compare(left.FullDescription, right.FullDescription); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = ReportingConfigurationSortingComparer.Instance.Compare(left.DefaultConfiguration, right.DefaultConfiguration); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = string.Compare(left.HelpUri.OriginalString, right.HelpUri.OriginalString); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = MultiformatMessageStringSortingComparer.Instance.Compare(left.Help, right.Help); - if (compareResult != 0) - { - return compareResult; - } - - return compareResult; - } - } -} diff --git a/src/Sarif/Comparers/ResultComparer.cs b/src/Sarif/Comparers/ResultComparer.cs new file mode 100644 index 000000000..579a46684 --- /dev/null +++ b/src/Sarif/Comparers/ResultComparer.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +/// +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. +/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 +/// +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class ResultComparer : IComparer + { + internal static readonly ResultComparer Instance = new ResultComparer(); + + public int Compare(Result left, Result right) + { + if (ComparerHelper.CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = string.Compare(left.RuleId, right.RuleId); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = left.RuleIndex.CompareTo(right.RuleIndex); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = left.Level.CompareTo(right.Level); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = left.Kind.CompareTo(right.Kind); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = MessageComparer.Instance.Compare(left.Message, right.Message); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ArtifactLocationComparer.Instance.Compare(left.AnalysisTarget, right.AnalysisTarget); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.Locations, right.Locations, LocationComparer.Instance); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.Guid, right.Guid); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.CorrelationGuid, right.CorrelationGuid); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = left.OccurrenceCount.CompareTo(right.OccurrenceCount); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.CodeFlows, right.CodeFlows, CodeFlowComparer.Instance); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = left.BaselineState.CompareTo(right.BaselineState); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = left.Rank.CompareTo(right.Rank); + + if (compareResult != 0) + { + return compareResult; + } + + // Warning there may be properties are not compared. + return compareResult; + } + } +} diff --git a/src/Sarif/Comparers/ResultSortingComparer.cs b/src/Sarif/Comparers/ResultSortingComparer.cs deleted file mode 100644 index 4ff39b64a..000000000 --- a/src/Sarif/Comparers/ResultSortingComparer.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -/// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. -/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 -/// -namespace Microsoft.CodeAnalysis.Sarif -{ - internal class ResultSortingComparer : IComparer - { - internal static readonly ResultSortingComparer Instance = new ResultSortingComparer(); - - public int Compare(Result left, Result right) - { - // both reference to same object, or both are null - if (ReferenceEquals(left, right)) - { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; - } - - int compareResult = 0; - - compareResult = string.Compare(left.RuleId, right.RuleId); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = left.RuleIndex.CompareTo(right.RuleIndex); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = left.Level.CompareTo(right.Level); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = left.Kind.CompareTo(right.Kind); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = MessageSortingComparer.Instance.Compare(left.Message, right.Message); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = ArtifactLocationSortingComparer.Instance.Compare(left.AnalysisTarget, right.AnalysisTarget); - if (compareResult != 0) - { - return compareResult; - } - - if (!object.ReferenceEquals(left.Locations, right.Locations)) - { - if (left.Locations == null) - { - return -1; - } - - if (right.Locations == null) - { - return 1; - } - - compareResult = left.Locations.Count.CompareTo(right.Locations.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int i = 0; i < left.Locations.Count; ++i) - { - compareResult = LocationSortingComparer.Instance.Compare(left.Locations[i], right.Locations[i]); - if (compareResult != 0) - { - return compareResult; - } - } - } - - compareResult = string.Compare(left.Guid, right.Guid); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = string.Compare(left.CorrelationGuid, right.CorrelationGuid); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = left.OccurrenceCount.CompareTo(right.OccurrenceCount); - if (compareResult != 0) - { - return compareResult; - } - - if (!object.ReferenceEquals(left.CodeFlows, right.CodeFlows)) - { - if (left.CodeFlows == null) - { - return -1; - } - - if (right.CodeFlows == null) - { - return 1; - } - - compareResult = left.CodeFlows.Count.CompareTo(right.CodeFlows.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int i = 0; i < left.CodeFlows.Count; ++i) - { - compareResult = CodeFlowSortingComparer.Instance.Compare(left.CodeFlows[i], right.CodeFlows[i]); - if (compareResult != 0) - { - return compareResult; - } - } - } - - compareResult = left.BaselineState.CompareTo(right.BaselineState); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = left.Rank.CompareTo(right.Rank); - if (compareResult != 0) - { - return compareResult; - } - - return compareResult; - } - } -} diff --git a/src/Sarif/Comparers/RunComparer.cs b/src/Sarif/Comparers/RunComparer.cs new file mode 100644 index 000000000..7e674c2ca --- /dev/null +++ b/src/Sarif/Comparers/RunComparer.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +/// +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. +/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 +/// +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class RunComparer : IComparer + { + internal static readonly RunComparer Instance = new RunComparer(); + + public int Compare(Run left, Run right) + { + if (ComparerHelper.CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.Artifacts, right.Artifacts, ArtifactComparer.Instance); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ToolComponentComparer.Instance.Compare(left?.Tool?.Driver, right?.Tool?.Driver); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.Results, right.Results, ResultComparer.Instance); + + if (compareResult != 0) + { + return compareResult; + } + + // Warning there may be properties are not compared. + return compareResult; + } + } +} diff --git a/src/Sarif/Comparers/RunSortingComparer.cs b/src/Sarif/Comparers/RunSortingComparer.cs deleted file mode 100644 index c666437df..000000000 --- a/src/Sarif/Comparers/RunSortingComparer.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -/// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. -/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 -/// -namespace Microsoft.CodeAnalysis.Sarif -{ - internal class RunSortingComparer : IComparer - { - internal static readonly RunSortingComparer Instance = new RunSortingComparer(); - - public int Compare(Run left, Run right) - { - if (ReferenceEquals(left, right)) - { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; - } - - int compareResult = 0; - - if (!object.ReferenceEquals(left.Tool.Driver.Rules, right.Tool.Driver.Rules)) - { - if (left.Tool.Driver.Rules == null) - { - return -1; - } - - if (right.Tool.Driver.Rules == null) - { - return 1; - } - - compareResult = left.Tool.Driver.Rules.Count.CompareTo(right.Tool.Driver.Rules.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int i = 0; i < left.Tool.Driver.Rules.Count; ++i) - { - compareResult = ReportingDescriptorSortingComparer.Instance.Compare(left.Tool.Driver.Rules[i], right.Tool.Driver.Rules[i]); - if (compareResult != 0) - { - return compareResult; - } - } - } - - return compareResult; - } - } -} diff --git a/src/Sarif/Comparers/ThreadFlowComparer.cs b/src/Sarif/Comparers/ThreadFlowComparer.cs new file mode 100644 index 000000000..512ce96da --- /dev/null +++ b/src/Sarif/Comparers/ThreadFlowComparer.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +/// +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. +/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 +/// +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class ThreadFlowComparer : IComparer + { + internal static readonly ThreadFlowComparer Instance = new ThreadFlowComparer(); + + public int Compare(ThreadFlow left, ThreadFlow right) + { + if (ComparerHelper.CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = string.Compare(left.Id, right.Id); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = MessageComparer.Instance.Compare(left.Message, right.Message); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.Locations, right.Locations, ThreadFlowLocationComparer.Instance); + + if (compareResult != 0) + { + return compareResult; + } + + // Warning there may be properties are not compared. + return compareResult; + } + } +} diff --git a/src/Sarif/Comparers/ThreadFlowLocationComparer.cs b/src/Sarif/Comparers/ThreadFlowLocationComparer.cs new file mode 100644 index 000000000..621ec30dd --- /dev/null +++ b/src/Sarif/Comparers/ThreadFlowLocationComparer.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +/// +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. +/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 +/// +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class ThreadFlowLocationComparer : IComparer + { + internal static readonly ThreadFlowLocationComparer Instance = new ThreadFlowLocationComparer(); + + public int Compare(ThreadFlowLocation left, ThreadFlowLocation right) + { + if (ComparerHelper.CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = left.Index.CompareTo(right.Index); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = LocationComparer.Instance.Compare(left.Location, right.Location); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.Kinds, right.Kinds); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = left.NestingLevel.CompareTo(right.NestingLevel); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = left.ExecutionOrder.CompareTo(right.ExecutionOrder); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = left.ExecutionTimeUtc.CompareTo(right.ExecutionTimeUtc); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = left.Importance.CompareTo(right.Importance); + + if (compareResult != 0) + { + return compareResult; + } + + // Warning there may be properties are not compared. + return compareResult; + } + } +} diff --git a/src/Sarif/Comparers/ThreadFlowLocationSortingComparer.cs b/src/Sarif/Comparers/ThreadFlowLocationSortingComparer.cs deleted file mode 100644 index c8e9c75fc..000000000 --- a/src/Sarif/Comparers/ThreadFlowLocationSortingComparer.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -/// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. -/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 -/// -namespace Microsoft.CodeAnalysis.Sarif -{ - internal class ThreadFlowLocationSortingComparer : IComparer - { - internal static readonly ThreadFlowLocationSortingComparer Instance = new ThreadFlowLocationSortingComparer(); - - public int Compare(ThreadFlowLocation left, ThreadFlowLocation right) - { - if (ReferenceEquals(left, right)) - { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; - } - - int compareResult = 0; - compareResult = left.Index.CompareTo(right.Index); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = LocationSortingComparer.Instance.Compare(left.Location, right.Location); - if (compareResult != 0) - { - return compareResult; - } - - if (!object.ReferenceEquals(left.Kinds, right.Kinds)) - { - if (left.Kinds == null) - { - return -1; - } - - if (right.Kinds == null) - { - return 1; - } - - compareResult = left.Kinds.Count.CompareTo(right.Kinds.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int i = 0; i < left.Kinds.Count; ++i) - { - compareResult = string.Compare(left.Kinds[i], right.Kinds[i]); - if (compareResult != 0) - { - return compareResult; - } - } - } - - compareResult = left.NestingLevel.CompareTo(right.NestingLevel); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = left.ExecutionOrder.CompareTo(right.ExecutionOrder); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = left.ExecutionTimeUtc.CompareTo(right.ExecutionTimeUtc); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = left.Importance.CompareTo(right.Importance); - if (compareResult != 0) - { - return compareResult; - } - - return compareResult; - } - } -} diff --git a/src/Sarif/Comparers/ThreadFlowSortingComparer.cs b/src/Sarif/Comparers/ThreadFlowSortingComparer.cs deleted file mode 100644 index db2035917..000000000 --- a/src/Sarif/Comparers/ThreadFlowSortingComparer.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -/// -/// All Comparer implementations should be replaced by auto-generated codes by JSchema as -/// part of EqualityComparer in a planned comprehensive solution. -/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 -/// -namespace Microsoft.CodeAnalysis.Sarif -{ - internal class ThreadFlowSortingComparer : IComparer - { - internal static readonly ThreadFlowSortingComparer Instance = new ThreadFlowSortingComparer(); - - public int Compare(ThreadFlow left, ThreadFlow right) - { - if (ReferenceEquals(left, right)) - { - return 0; - } - - if (left == null) - { - return -1; - } - - if (right == null) - { - return 1; - } - - int compareResult = 0; - compareResult = string.Compare(left.Id, right.Id); - if (compareResult != 0) - { - return compareResult; - } - - compareResult = MessageSortingComparer.Instance.Compare(left.Message, right.Message); - if (compareResult != 0) - { - return compareResult; - } - - if (!object.ReferenceEquals(left.Locations, right.Locations)) - { - if (left.Locations == null) - { - return -1; - } - - if (right.Locations == null) - { - return 1; - } - - compareResult = left.Locations.Count.CompareTo(right.Locations.Count); - if (compareResult != 0) - { - return compareResult; - } - - for (int i = 0; i < left.Locations.Count; ++i) - { - compareResult = ThreadFlowLocationSortingComparer.Instance.Compare(left.Locations[i], right.Locations[i]); - if (compareResult != 0) - { - return compareResult; - } - } - } - - return compareResult; - } - } -} diff --git a/src/Sarif/Comparers/ToolComponentComparer.cs b/src/Sarif/Comparers/ToolComponentComparer.cs new file mode 100644 index 000000000..0e0f1c548 --- /dev/null +++ b/src/Sarif/Comparers/ToolComponentComparer.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +/// +/// Warning this comparer may not have all properties compared. Will be replaced by comprehensive +/// comparer generated by JSchema as part of EqualityComparer in a planned comprehensive solution. +/// Tracking by issue: https://github.com/microsoft/jschema/issues/141 +/// + +namespace Microsoft.CodeAnalysis.Sarif.Comparers +{ + internal class ToolComponentComparer : IComparer + { + internal static readonly ToolComponentComparer Instance = new ToolComponentComparer(); + + public int Compare(ToolComponent left, ToolComponent right) + { + if (ComparerHelper.CompareReference(left, right, out int compareResult)) + { + return compareResult; + } + + compareResult = string.Compare(left.Guid, right.Guid); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.Name, right.Name); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.Organization, right.Organization); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.Product, right.Product); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.FullName, right.FullName); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.Version, right.Version); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.SemanticVersion, right.SemanticVersion); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = string.Compare(left.ReleaseDateUtc, right.ReleaseDateUtc); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareUri(left.DownloadUri, right.DownloadUri); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareUri(left.InformationUri, right.InformationUri); + + if (compareResult != 0) + { + return compareResult; + } + + compareResult = ComparerHelper.CompareList(left.Rules, right.Rules, ReportingDescriptorComparer.Instance); + + if (compareResult != 0) + { + return compareResult; + } + + // Warning there may be properties are not compared. + return compareResult; + } + } +} diff --git a/src/Sarif/Visitors/SortingVisitor.cs b/src/Sarif/Visitors/SortingVisitor.cs index a0f3c295a..81ad4eb09 100644 --- a/src/Sarif/Visitors/SortingVisitor.cs +++ b/src/Sarif/Visitors/SortingVisitor.cs @@ -5,66 +5,51 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.CodeAnalysis.Sarif.Comparers; + namespace Microsoft.CodeAnalysis.Sarif.Visitors { public class SortingVisitor : SarifRewritingVisitor { + // Dictionaries to cache new index to old index mappings. private readonly IDictionary ruleIndexMap; private readonly IDictionary artifactIndexMap; public SortingVisitor() { - ruleIndexMap = new ConcurrentDictionary(); - artifactIndexMap = new ConcurrentDictionary(); + this.ruleIndexMap = new ConcurrentDictionary(); + this.artifactIndexMap = new ConcurrentDictionary(); } public override SarifLog VisitSarifLog(SarifLog node) { SarifLog current = base.VisitSarifLog(node); - if (node?.Runs != null) + + if (current?.Runs != null) { - current.Runs = current.Runs.OrderBy(r => r, RunSortingComparer.Instance).ToList(); + current.Runs = current.Runs.OrderBy(r => r, RunComparer.Instance).ToList(); } + return current; } public override Run VisitRun(Run node) { + // Reset index maps for each run object. + this.ruleIndexMap.Clear(); + this.artifactIndexMap.Clear(); + if (node?.Artifacts != null) { - IDictionary oldIndexes = new Dictionary(capacity: node.Artifacts.Count); - // save old indexes - for (int i = 0; i < node.Artifacts.Count; i++) - { - if (!oldIndexes.ContainsKey(node.Artifacts[i])) - { - oldIndexes.Add(node.Artifacts[i], i); - } - } - - // sort - node.Artifacts = node.Artifacts.OrderBy(a => a, ArtifactSortingComparer.Instance).ToList(); - - // udpate new indexes - for (int newIndex = 0; newIndex < node.Artifacts.Count; newIndex++) - { - if (oldIndexes.TryGetValue(node.Artifacts[newIndex], out int oldIndex) - && !artifactIndexMap.TryGetValue(oldIndex, out _)) - { - artifactIndexMap.Add(oldIndex, newIndex); - } - } - - oldIndexes.Clear(); + node.Artifacts = this.SortAndBuildIndexMap(node?.Artifacts, ArtifactComparer.Instance, this.artifactIndexMap); } - // traverse child nodes first, so the child list properties should be sorted + // Traverse child nodes first to make sure the child properties are sorted. Run current = base.VisitRun(node); - // then sort properties of current node if (current?.Results != null) { - current.Results = current.Results.OrderBy(r => r, ResultSortingComparer.Instance).ToList(); + current.Results = current.Results.OrderBy(r => r, ResultComparer.Instance).ToList(); } return current; @@ -72,107 +57,133 @@ public override Run VisitRun(Run node) public override ToolComponent VisitToolComponent(ToolComponent node) { - ToolComponent current = base.VisitToolComponent(node); - if (current?.Rules != null) + if (node?.Rules != null) { - IDictionary oldIndexes = new Dictionary(capacity: current.Rules.Count); - // before sort the rules, save old indexes - for (int i = 0; i < current.Rules.Count; i++) - { - if (!oldIndexes.ContainsKey(current.Rules[i])) - { - oldIndexes.Add(current.Rules[i], i); - } - } - - // sort rules - current.Rules = current.Rules.OrderBy(r => r, ReportingDescriptorSortingComparer.Instance).ToList(); - - // udpate new indexes - for (int newIndex = 0; newIndex < current.Rules.Count; newIndex++) - { - if (oldIndexes.TryGetValue(current.Rules[newIndex], out int oldIndex) - && !ruleIndexMap.TryGetValue(oldIndex, out _)) - { - ruleIndexMap.Add(oldIndex, newIndex); - } - } - - oldIndexes.Clear(); + node.Rules = this.SortAndBuildIndexMap(node?.Rules, ReportingDescriptorComparer.Instance, this.ruleIndexMap); } - return current; + + return base.VisitToolComponent(node); } public override Result VisitResult(Result node) { Result current = base.VisitResult(node); + if (current != null) { - // update old index to new index - if (current.RuleIndex != -1 - && ruleIndexMap.TryGetValue(current.RuleIndex, out int newIndex)) + if (current.RuleIndex != -1 && this.ruleIndexMap.TryGetValue(current.RuleIndex, out int newIndex)) { current.RuleIndex = newIndex; } if (current.Locations != null) { - current.Locations = current.Locations.OrderBy(r => r, LocationSortingComparer.Instance).ToList(); + current.Locations = current.Locations.OrderBy(r => r, LocationComparer.Instance).ToList(); } + if (current.CodeFlows != null) { - current.CodeFlows = current.CodeFlows.OrderBy(r => r, CodeFlowSortingComparer.Instance).ToList(); + current.CodeFlows = current.CodeFlows.OrderBy(r => r, CodeFlowComparer.Instance).ToList(); } } + return current; } public override CodeFlow VisitCodeFlow(CodeFlow node) { CodeFlow current = base.VisitCodeFlow(node); + if (current?.ThreadFlows != null) { - current.ThreadFlows = current.ThreadFlows.OrderBy(t => t, ThreadFlowSortingComparer.Instance).ToList(); + current.ThreadFlows = current.ThreadFlows.OrderBy(t => t, ThreadFlowComparer.Instance).ToList(); } + return current; } public override ThreadFlow VisitThreadFlow(ThreadFlow node) { ThreadFlow current = base.VisitThreadFlow(node); + if (current?.Locations != null) { - current.Locations = current.Locations.OrderBy(t => t, ThreadFlowLocationSortingComparer.Instance).ToList(); + current.Locations = current.Locations.OrderBy(t => t, ThreadFlowLocationComparer.Instance).ToList(); } - return current; - } - public override ThreadFlowLocation VisitThreadFlowLocation(ThreadFlowLocation node) - { - return base.VisitThreadFlowLocation(node); + return current; } public override Location VisitLocation(Location node) { Location current = base.VisitLocation(node); + if (current?.LogicalLocations != null) { - current.LogicalLocations = current.LogicalLocations.OrderBy(t => t, LogicalLocationSortingComparer.Instance).ToList(); + current.LogicalLocations = current.LogicalLocations.OrderBy(t => t, LogicalLocationComparer.Instance).ToList(); } + return current; } public override ArtifactLocation VisitArtifactLocation(ArtifactLocation node) { ArtifactLocation current = base.VisitArtifactLocation(node); - // update old index to new index - if (current.Index != -1 - && artifactIndexMap.TryGetValue(current.Index, out int newIndex)) + + if (current.Index != -1 && this.artifactIndexMap.TryGetValue(current.Index, out int newIndex)) { current.Index = newIndex; } return current; } + + private IList SortAndBuildIndexMap(IList list, IComparer comparer, IDictionary indexMapping) + { + if (list != null) + { + IDictionary unsortedIndices = this.CacheListIndices(list); + + list = list.OrderBy(r => r, comparer).ToList(); + + this.MapNewIndices(list, unsortedIndices, indexMapping); + + unsortedIndices.Clear(); + } + + return list; + } + + private IDictionary CacheListIndices(IList list) + { + // Assume each item in the list is unique (has different reference). + // According to sarif-2.1.0-rtm.5.json, artifacts array of runs and rules array of toolComponent + // are defined as "uniqueItems". + var dict = new Dictionary(capacity: list.Count); + + for (int i = 0; i < list.Count; i++) + { + // If some objects are same (with same reference), keep only 1 object + // in the dictionary and set of indices in hash set. + if (!dict.ContainsKey(list[i])) + { + dict.Add(list[i], i); + } + } + + return dict; + } + + private void MapNewIndices(IList newList, IDictionary oldIndices, IDictionary indexMapping) + { + for (int newIndex = 0; newIndex < newList.Count; newIndex++) + { + if (oldIndices.TryGetValue(newList[newIndex], out int oldIndex) && + !indexMapping.ContainsKey(oldIndex)) + { + indexMapping.Add(oldIndex, newIndex); + } + } + } } } diff --git a/src/Test.UnitTests.Sarif/Comparers/ComparersTests.cs b/src/Test.UnitTests.Sarif/Comparers/ComparersTests.cs new file mode 100644 index 000000000..94f5c9b2b --- /dev/null +++ b/src/Test.UnitTests.Sarif/Comparers/ComparersTests.cs @@ -0,0 +1,668 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +using FluentAssertions; + +using Microsoft.CodeAnalysis.Sarif; +using Microsoft.CodeAnalysis.Sarif.Comparers; +using Microsoft.CodeAnalysis.Test.Utilities.Sarif; + +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.CodeAnalysis.Test.UnitTests.Sarif.Comparers +{ + public class ComparersTests + { + private readonly Random random; + + public ComparersTests(ITestOutputHelper outputHelper) + { + this.random = RandomSarifLogGenerator.GenerateRandomAndLog(outputHelper); + } + + [Fact] + public void CompareList__Shuffle_Tests() + { + IList originalList = Enumerable.Range(-100, 200).ToList(); + + IList shuffledList = originalList.ToList().Shuffle(this.random); + + int result = 0, newResult = 0; + + result = ComparerHelper.CompareList(originalList, shuffledList); + result.Should().NotBe(0); + + newResult = ComparerHelper.CompareList(shuffledList, originalList); + newResult.Should().Be(result * -1); + + IList sortedList = shuffledList.OrderBy(i => i).ToList(); + + result = ComparerHelper.CompareList(originalList, sortedList); + result.Should().Be(0); + + newResult = ComparerHelper.CompareList(originalList, sortedList); + newResult.Should().Be(0); + } + + [Fact] + public void CompareList_BothNull_Tests() + { + IList list1 = null; + IList list2 = null; + ComparerHelper.CompareList(list1, list2).Should().Be(0); + ComparerHelper.CompareList(list2, list1).Should().Be(0); + } + + [Fact] + public void CompareList_CompareNullToNotNull_Tests() + { + IList list1 = null; + IList list2 = Enumerable.Range(-10, 20).ToList(); + ComparerHelper.CompareList(list1, list2).Should().Be(-1); + ComparerHelper.CompareList(list2, list1).Should().Be(1); + } + + [Fact] + public void CompareList_DifferentCount_Tests() + { + IList list1 = Enumerable.Range(0, 11).ToList(); + IList list2 = Enumerable.Range(0, 10).ToList(); + ComparerHelper.CompareList(list1, list2).Should().Be(1); + ComparerHelper.CompareList(list2, list1).Should().Be(-1); + } + + [Fact] + public void CompareList_SameCountDifferentElement_Tests() + { + IList list1 = Enumerable.Range(0, 10).ToList(); + IList list2 = Enumerable.Range(1, 10).ToList(); + ComparerHelper.CompareList(list1, list2).Should().Be(-1); + ComparerHelper.CompareList(list2, list1).Should().Be(1); + } + + [Fact] + public void CompareList_WithNullComparer_Tests() + { + ToolComponent tool = new ToolComponent { Guid = Guid.Empty.ToString() }; + + IList runs1 = new[] { new Run { Tool = new Tool { Driver = tool } } }; + IList runs2 = Array.Empty(); + + Action act = () => ComparerHelper.CompareList(runs1, runs2, comparer: null); + act.Should().Throw(); + } + + [Fact] + public void CompareList_WithComparer_Tests() + { + IList run1 = new List(); + run1.Add(null); + run1.Add(new Run { Tool = new Tool { Driver = new ToolComponent { Guid = Guid.NewGuid().ToString() } } }); + + IList run2 = new List(); + run2.Add(new Run { Tool = new Tool { Driver = new ToolComponent { Guid = Guid.NewGuid().ToString() } } }); + run2.Add(null); + + int result = ComparerHelper.CompareList(run1, run2, RunComparer.Instance); + result.Should().Be(-1); + + result = ComparerHelper.CompareList(run2, run1, RunComparer.Instance); + result.Should().Be(1); + } + + [Fact] + public void CompareDictionary_Shuffle_Tests() + { + IDictionary dict1 = new Dictionary(); + dict1.Add("a", "a"); + dict1.Add("b", "b"); + dict1.Add("c", "c"); + + IDictionary dict2 = new Dictionary(); + dict2.Add("b", "b"); + dict2.Add("c", "c"); + dict2.Add("a", "a"); + + int result = ComparerHelper.CompareDictionary(dict1, dict2); + result.Should().Be(0); + + dict2["c"] = "d"; + + result = ComparerHelper.CompareDictionary(dict1, dict2); + result.Should().Be(-1); + + result = ComparerHelper.CompareDictionary(dict2, dict1); + result.Should().Be(1); + } + + [Fact] + public void CompareDictionary_BothNull_Tests() + { + IDictionary list1 = null; + IDictionary list2 = null; + ComparerHelper.CompareDictionary(list1, list2).Should().Be(0); + ComparerHelper.CompareDictionary(list2, list1).Should().Be(0); + } + + [Fact] + public void CompareDictionary_CompareNullToNotNull_Tests() + { + IDictionary list1 = null; + IDictionary list2 = new Dictionary() + { { "a", "a" } }; + ComparerHelper.CompareDictionary(list1, list2).Should().Be(-1); + ComparerHelper.CompareDictionary(list2, list1).Should().Be(1); + } + + [Fact] + public void CompareDictionary_DifferentCount_Tests() + { + IDictionary list1 = new Dictionary() + { { "a", "a" }, { "b", "b" } }; + IDictionary list2 = new Dictionary() + { { "c", "c" } }; + ComparerHelper.CompareDictionary(list1, list2).Should().Be(1); + ComparerHelper.CompareDictionary(list2, list1).Should().Be(-1); + } + + [Fact] + public void CompareDictionary_SameCountDifferentElement_Tests() + { + IDictionary list1 = new Dictionary() + { { "a", "a" }, { "b", "b" } }; + IDictionary list2 = new Dictionary() + { { "c", "c" }, { "d", "d" } }; + ComparerHelper.CompareDictionary(list1, list2).Should().Be(-1); + ComparerHelper.CompareDictionary(list2, list1).Should().Be(1); + } + + [Fact] + public void CompareDictionary_WithNullComparer_Tests() + { + IDictionary loc1 = new Dictionary(); + IDictionary loc2 = new Dictionary(); + + Action act = () => ComparerHelper.CompareDictionary(loc1, loc2, comparer: null); + act.Should().Throw(); + } + + [Fact] + public void CompareDictionary_WithComparer_Tests() + { + IDictionary loc1 = new Dictionary(); + loc1.Add("1", null); + loc1.Add("2", new Location { Id = 2 }); + IDictionary loc2 = new Dictionary(); + loc2.Add("1", new Location { Id = 1 }); + loc2.Add("2", new Location { Id = 2 }); + + int result = ComparerHelper.CompareDictionary(loc1, loc2, LocationComparer.Instance); + result.Should().Be(-1); + + result = ComparerHelper.CompareDictionary(loc2, loc1, LocationComparer.Instance); + result.Should().Be(1); + } + + [Fact] + public void ArtifactContentComparer_Tests() + { + var list1 = new List(); + var list2 = new List(); + + list1.Add(null); + list2.Add(null); + ComparerHelper.CompareList(list1, list2, ArtifactContentComparer.Instance).Should().Be(0); + + list1.Add(new ArtifactContent() { Text = "content 1" }); + list2.Add(new ArtifactContent() { Text = "content 2" }); + + ComparerHelper.CompareList(list1, list2, ArtifactContentComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(list2, list1, ArtifactContentComparer.Instance).Should().Be(1); + list1.Clear(); + list2.Clear(); + + list1.Add(new ArtifactContent() { Binary = "WUJDMTIz" }); + list2.Add(new ArtifactContent() { Binary = "QUJDMTIz" }); + + ComparerHelper.CompareList(list1, list2, ArtifactContentComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(list2, list1, ArtifactContentComparer.Instance).Should().Be(-1); + list1.Clear(); + list2.Clear(); + + list1.Add(new ArtifactContent() { Text = "content 1", Rendered = new MultiformatMessageString { Markdown = "`markdown`" } }); + list2.Add(new ArtifactContent() { Text = "content 1", Rendered = new MultiformatMessageString { Markdown = "title" } }); + ComparerHelper.CompareList(list1, list2, ArtifactContentComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(list2, list1, ArtifactContentComparer.Instance).Should().Be(1); + } + + [Fact] + public void ReportingConfigurationComparer_Tests() + { + var rules1 = new List(); + var rules2 = new List(); + + rules1.Add(null); + rules2.Add(null); + ComparerHelper.CompareList(rules1, rules2, ReportingConfigurationComparer.Instance).Should().Be(0); + + rules1.Add(new ReportingConfiguration() { Rank = 26.648d }); + rules2.Add(new ReportingConfiguration() { Rank = 87.1d }); + + ComparerHelper.CompareList(rules1, rules2, ReportingConfigurationComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(rules2, rules1, ReportingConfigurationComparer.Instance).Should().Be(1); + + + rules1.Insert(0, new ReportingConfiguration() { Level = FailureLevel.Error }); + rules2.Insert(0, new ReportingConfiguration() { Level = FailureLevel.Warning }); + + ComparerHelper.CompareList(rules1, rules2, ReportingConfigurationComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(rules2, rules1, ReportingConfigurationComparer.Instance).Should().Be(-1); + + rules1.Insert(0, new ReportingConfiguration() { Enabled = false, Rank = 80d }); + rules2.Insert(0, new ReportingConfiguration() { Enabled = true, Rank = 80d }); + ComparerHelper.CompareList(rules1, rules2, ReportingConfigurationComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(rules2, rules1, ReportingConfigurationComparer.Instance).Should().Be(1); + } + + [Fact] + public void ToolComponentComparer_Tests() + { + var list1 = new List(); + var list2 = new List(); + + list1.Add(null); + list2.Add(null); + ComparerHelper.CompareList(list1, list2, ToolComponentComparer.Instance).Should().Be(0); + + list1.Insert(0, new ToolComponent() { Guid = Guid.Empty.ToString() }); + list2.Insert(0, new ToolComponent() { Guid = Guid.NewGuid().ToString() }); + + ComparerHelper.CompareList(list1, list2, ToolComponentComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(list2, list1, ToolComponentComparer.Instance).Should().Be(1); + + + list1.Insert(0, new ToolComponent() { Name = "scan tool" }); + list2.Insert(0, new ToolComponent() { Name = "code scan tool" }); + + ComparerHelper.CompareList(list1, list2, ToolComponentComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(list2, list1, ToolComponentComparer.Instance).Should().Be(-1); + + list1.Insert(0, new ToolComponent() { Organization = "MS", Name = "scan tool" }); + list2.Insert(0, new ToolComponent() { Organization = "Microsoft", Name = "scan tool" }); + + ComparerHelper.CompareList(list1, list2, ToolComponentComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(list2, list1, ToolComponentComparer.Instance).Should().Be(-1); + + list1.Insert(0, new ToolComponent() { Product = "PREfast", Name = "scan tool" }); + list2.Insert(0, new ToolComponent() { Product = "prefast", Name = "scan tool" }); + + ComparerHelper.CompareList(list1, list2, ToolComponentComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(list2, list1, ToolComponentComparer.Instance).Should().Be(-1); + + list1.Insert(0, new ToolComponent() { FullName = "Analysis Linter", Name = "scan tool" }); + list2.Insert(0, new ToolComponent() { FullName = "Analysis Linter Tool", Name = "scan tool" }); + + ComparerHelper.CompareList(list1, list2, ToolComponentComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(list2, list1, ToolComponentComparer.Instance).Should().Be(1); + + list1.Insert(0, new ToolComponent() { Version = "CWR-2022-01", Name = "scan tool" }); + list2.Insert(0, new ToolComponent() { Version = "CWR-2021-12", Name = "scan tool" }); + + ComparerHelper.CompareList(list1, list2, ToolComponentComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(list2, list1, ToolComponentComparer.Instance).Should().Be(-1); + + list1.Insert(0, new ToolComponent() { SemanticVersion = "1.0.1", Name = "scan tool" }); + list2.Insert(0, new ToolComponent() { SemanticVersion = "1.0.3", Name = "scan tool" }); + + ComparerHelper.CompareList(list1, list2, ToolComponentComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(list2, list1, ToolComponentComparer.Instance).Should().Be(1); + + list1.Insert(0, new ToolComponent() { ReleaseDateUtc = "2/8/2022", Name = "scan tool" }); + list2.Insert(0, new ToolComponent() { ReleaseDateUtc = "1/1/2022", Name = "scan tool" }); + + ComparerHelper.CompareList(list1, list2, ToolComponentComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(list2, list1, ToolComponentComparer.Instance).Should().Be(-1); + + list1.Insert(0, new ToolComponent() { DownloadUri = new Uri("https://example/download/v1"), Name = "scan tool" }); + list2.Insert(0, new ToolComponent() { DownloadUri = new Uri("https://example/download/v2"), Name = "scan tool" }); + + ComparerHelper.CompareList(list1, list2, ToolComponentComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(list2, list1, ToolComponentComparer.Instance).Should().Be(1); + + list1.Insert(0, new ToolComponent() { Rules = new ReportingDescriptor[] { new ReportingDescriptor { Id = "TESTRULE001" } }, Name = "scan tool" }); + list2.Insert(0, new ToolComponent() { Rules = new ReportingDescriptor[] { new ReportingDescriptor { Id = "TESTRULE002" } }, Name = "scan tool" }); + + ComparerHelper.CompareList(list1, list2, ToolComponentComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(list2, list1, ToolComponentComparer.Instance).Should().Be(1); + } + + [Fact] + public void ReportingDescriptorComparer_Tests() + { + var rules1 = new List(); + var rules2 = new List(); + + rules1.Add(null); + rules2.Add(null); + ComparerHelper.CompareList(rules1, rules2, ReportingDescriptorComparer.Instance).Should().Be(0); + + rules1.Insert(0, new ReportingDescriptor() { Id = "TestRule1" }); + rules2.Insert(0, new ReportingDescriptor() { Id = "TestRule2" }); + + ComparerHelper.CompareList(rules1, rules2, ReportingDescriptorComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(rules2, rules1, ReportingDescriptorComparer.Instance).Should().Be(1); + + + rules1.Insert(0, new ReportingDescriptor() { DeprecatedIds = new string[] { "OldRuleId3" }, Id = "TestRule1" }); + rules2.Insert(0, new ReportingDescriptor() { DeprecatedIds = new string[] { "OldRuleId2" }, Id = "TestRule1" }); + + ComparerHelper.CompareList(rules1, rules2, ReportingDescriptorComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(rules2, rules1, ReportingDescriptorComparer.Instance).Should().Be(-1); + + rules1.Insert(0, new ReportingDescriptor() { Guid = Guid.NewGuid().ToString(), Id = "TestRule1" }); + rules2.Insert(0, new ReportingDescriptor() { Guid = Guid.Empty.ToString(), Id = "TestRule1" }); + + ComparerHelper.CompareList(rules1, rules2, ReportingDescriptorComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(rules2, rules1, ReportingDescriptorComparer.Instance).Should().Be(-1); + + rules1.Insert(0, new ReportingDescriptor() { DeprecatedIds = new string[] { Guid.Empty.ToString() }, Id = "TestRule1" }); + rules2.Insert(0, new ReportingDescriptor() { DeprecatedIds = new string[] { Guid.NewGuid().ToString() }, Id = "TestRule1" }); + + ComparerHelper.CompareList(rules1, rules2, ReportingDescriptorComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(rules2, rules1, ReportingDescriptorComparer.Instance).Should().Be(1); + + rules1.Insert(0, new ReportingDescriptor() { Name = "UnusedVariable", Id = "TestRule1" }); + rules2.Insert(0, new ReportingDescriptor() { Name = "", Id = "TestRule1" }); + + ComparerHelper.CompareList(rules1, rules2, ReportingDescriptorComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(rules2, rules1, ReportingDescriptorComparer.Instance).Should().Be(-1); + + rules1.Insert(0, new ReportingDescriptor() { ShortDescription = new MultiformatMessageString { Text = "Remove unused variable" }, Id = "TestRule1" }); + rules2.Insert(0, new ReportingDescriptor() { ShortDescription = new MultiformatMessageString { Text = "Wrong description" }, Id = "TestRule1" }); + + ComparerHelper.CompareList(rules1, rules2, ReportingDescriptorComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(rules2, rules1, ReportingDescriptorComparer.Instance).Should().Be(1); + + rules1.Insert(0, new ReportingDescriptor() { FullDescription = new MultiformatMessageString { Text = "Remove unused variable" }, Id = "TestRule1" }); + rules2.Insert(0, new ReportingDescriptor() { FullDescription = new MultiformatMessageString { Text = "Wrong description" }, Id = "TestRule1" }); + + ComparerHelper.CompareList(rules1, rules2, ReportingDescriptorComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(rules2, rules1, ReportingDescriptorComparer.Instance).Should().Be(1); + + rules1.Insert(0, new ReportingDescriptor() { DefaultConfiguration = new ReportingConfiguration { Level = FailureLevel.Note }, Id = "TestRule1" }); + rules2.Insert(0, new ReportingDescriptor() { DefaultConfiguration = new ReportingConfiguration { Level = FailureLevel.Error }, Id = "TestRule1" }); + + ComparerHelper.CompareList(rules1, rules2, ReportingDescriptorComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(rules2, rules1, ReportingDescriptorComparer.Instance).Should().Be(1); + + rules1.Insert(0, new ReportingDescriptor() { HelpUri = new Uri("http://example.net/rule/id"), Id = "TestRule1" }); + rules2.Insert(0, new ReportingDescriptor() { HelpUri = new Uri("http://example.net"), Id = "TestRule1" }); + + ComparerHelper.CompareList(rules1, rules2, ReportingDescriptorComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(rules2, rules1, ReportingDescriptorComparer.Instance).Should().Be(-1); + + rules1.Insert(0, new ReportingDescriptor() { Help = new MultiformatMessageString { Text = "Helping texts." }, Id = "TestRule1" }); + rules2.Insert(0, new ReportingDescriptor() { Help = new MultiformatMessageString { Text = "For customers." }, Id = "TestRule1" }); + + ComparerHelper.CompareList(rules1, rules2, ReportingDescriptorComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(rules2, rules1, ReportingDescriptorComparer.Instance).Should().Be(-1); + } + + [Fact] + public void RegionComparer_Tests() + { + var regions1 = new List(); + var regions2 = new List(); + + regions1.Add(null); + regions2.Add(null); + ComparerHelper.CompareList(regions1, regions2, RegionComparer.Instance).Should().Be(0); + + regions1.Insert(0, new Region() { StartLine = 0, StartColumn = 0 }); + regions2.Insert(0, new Region() { StartLine = 1, StartColumn = 0 }); + + ComparerHelper.CompareList(regions1, regions2, RegionComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(regions2, regions1, RegionComparer.Instance).Should().Be(1); + + regions1.Insert(0, new Region() { StartLine = 0, StartColumn = 1 }); + regions2.Insert(0, new Region() { StartLine = 0, StartColumn = 0 }); + + ComparerHelper.CompareList(regions1, regions2, RegionComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(regions2, regions1, RegionComparer.Instance).Should().Be(-1); + + regions1.Insert(0, new Region() { StartLine = 10, EndLine = 11, StartColumn = 0 }); + regions2.Insert(0, new Region() { StartLine = 10, EndLine = 10, StartColumn = 0 }); + + ComparerHelper.CompareList(regions1, regions2, RegionComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(regions2, regions1, RegionComparer.Instance).Should().Be(-1); + + regions1.Insert(0, new Region() { StartLine = 10, EndLine = 10, StartColumn = 5, EndColumn = 23 }); + regions2.Insert(0, new Region() { StartLine = 10, EndLine = 10, StartColumn = 5, EndColumn = 7 }); + + ComparerHelper.CompareList(regions1, regions2, RegionComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(regions2, regions1, RegionComparer.Instance).Should().Be(-1); + + regions1.Insert(0, new Region() { CharOffset = 100, CharLength = 30 }); + regions2.Insert(0, new Region() { CharOffset = 36, CharLength = 30 }); + + ComparerHelper.CompareList(regions1, regions2, RegionComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(regions2, regions1, RegionComparer.Instance).Should().Be(-1); + + regions1.Insert(0, new Region() { CharOffset = 100, CharLength = 47 }); + regions2.Insert(0, new Region() { CharOffset = 100, CharLength = 326 }); + + ComparerHelper.CompareList(regions1, regions2, RegionComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(regions2, regions1, RegionComparer.Instance).Should().Be(1); + + regions1.Insert(0, new Region() { ByteOffset = 226, ByteLength = 11 }); + regions2.Insert(0, new Region() { ByteOffset = 1623, ByteLength = 11 }); + + ComparerHelper.CompareList(regions1, regions2, RegionComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(regions2, regions1, RegionComparer.Instance).Should().Be(1); + + regions1.Insert(0, new Region() { ByteOffset = 67, ByteLength = 9 }); + regions2.Insert(0, new Region() { CharOffset = 67, ByteLength = 11 }); + + ComparerHelper.CompareList(regions1, regions2, RegionComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(regions2, regions1, RegionComparer.Instance).Should().Be(1); + } + + [Fact] + public void ArtifactComparer_Tests() + { + var artifacts1 = new List(); + var artifacts2 = new List(); + + artifacts1.Add(null); + artifacts2.Add(null); + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(0); + + artifacts1.Insert(0, new Artifact() { Description = new Message { Text = "Represents for an artifact" } }); + artifacts2.Insert(0, new Artifact() { Description = new Message { Text = "A source file artifact" } }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(-1); + + artifacts1.Insert(0, new Artifact() { Location = new ArtifactLocation { Index = 0 } }); + artifacts2.Insert(0, new Artifact() { Location = new ArtifactLocation { Index = 1 } }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(1); + + artifacts1.Insert(0, new Artifact() { ParentIndex = 0 }); + artifacts2.Insert(0, new Artifact() { ParentIndex = 1 }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(1); + + artifacts1.Insert(0, new Artifact() { Offset = 2 }); + artifacts2.Insert(0, new Artifact() { Offset = 1 }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(-1); + + artifacts1.Insert(0, new Artifact() { Length = 102542 }); + artifacts2.Insert(0, new Artifact() { Length = -1 }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(-1); + + artifacts1.Insert(0, new Artifact() { Roles = ArtifactRoles.AnalysisTarget | ArtifactRoles.Attachment }); + artifacts2.Insert(0, new Artifact() { Roles = ArtifactRoles.Policy }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(1); + + artifacts1.Insert(0, new Artifact() { MimeType = "text" }); + artifacts2.Insert(0, new Artifact() { MimeType = "video" }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(1); + + artifacts1.Insert(0, new Artifact() { Contents = new ArtifactContent { Text = "\"string\"" } }); + artifacts2.Insert(0, new Artifact() { Contents = new ArtifactContent { Text = "var result = 0;" } }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(1); + + artifacts1.Insert(0, new Artifact() { Encoding = "UTF-16BE" }); + artifacts2.Insert(0, new Artifact() { Encoding = "UTF-16LE" }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(1); + + artifacts1.Insert(0, new Artifact() { SourceLanguage = "html" }); + artifacts2.Insert(0, new Artifact() { SourceLanguage = "csharp/7" }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(-1); + + artifacts1.Insert(0, new Artifact() { Hashes = new Dictionary { { "sha-256", "..." } } }); + artifacts2.Insert(0, new Artifact() { Hashes = new Dictionary { { "sha-512", "..." } } }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(1); + + artifacts1.Insert(0, new Artifact() { LastModifiedTimeUtc = DateTime.UtcNow }); + artifacts2.Insert(0, new Artifact() { LastModifiedTimeUtc = DateTime.UtcNow.AddDays(-1) }); + + ComparerHelper.CompareList(artifacts1, artifacts2, ArtifactComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(artifacts2, artifacts1, ArtifactComparer.Instance).Should().Be(-1); + } + + [Fact] + public void ThreadFlowLocationComparer_Tests() + { + var locations1 = new List(); + var locations2 = new List(); + + locations1.Add(null); + locations2.Add(null); + ComparerHelper.CompareList(locations1, locations2, ThreadFlowLocationComparer.Instance).Should().Be(0); + + Location loc1 = new Location + { + PhysicalLocation = new PhysicalLocation + { + ArtifactLocation = new ArtifactLocation + { + Uri = new Uri("path/to/file1.c", UriKind.Relative) + } + } + }; + + Location loc2 = new Location + { + PhysicalLocation = new PhysicalLocation + { + ArtifactLocation = new ArtifactLocation + { + Uri = new Uri("path/to/file2.c", UriKind.Relative) + } + } + }; + + locations1.Insert(0, new ThreadFlowLocation() { Location = loc1 }); + locations2.Insert(0, new ThreadFlowLocation() { Location = loc2 }); + + ComparerHelper.CompareList(locations1, locations2, ThreadFlowLocationComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(locations2, locations1, ThreadFlowLocationComparer.Instance).Should().Be(1); + + locations1.Insert(0, new ThreadFlowLocation() { Index = 2, Location = loc1 }); + locations2.Insert(0, new ThreadFlowLocation() { Index = 1, Location = loc2 }); + + ComparerHelper.CompareList(locations1, locations2, ThreadFlowLocationComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(locations2, locations1, ThreadFlowLocationComparer.Instance).Should().Be(-1); + + locations1.Insert(0, new ThreadFlowLocation() { Kinds = new string[] { "memory" }, Location = loc1 }); + locations2.Insert(0, new ThreadFlowLocation() { Kinds = new string[] { "call", "branch" }, Location = loc2 }); + + ComparerHelper.CompareList(locations1, locations2, ThreadFlowLocationComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(locations2, locations1, ThreadFlowLocationComparer.Instance).Should().Be(1); + + locations1.Insert(0, new ThreadFlowLocation() { NestingLevel = 3 }); + locations2.Insert(0, new ThreadFlowLocation() { NestingLevel = 2 }); + + ComparerHelper.CompareList(locations1, locations2, ThreadFlowLocationComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(locations2, locations1, ThreadFlowLocationComparer.Instance).Should().Be(-1); + + locations1.Insert(0, new ThreadFlowLocation() { ExecutionOrder = 2 }); + locations2.Insert(0, new ThreadFlowLocation() { ExecutionOrder = 1 }); + + ComparerHelper.CompareList(locations1, locations2, ThreadFlowLocationComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(locations2, locations1, ThreadFlowLocationComparer.Instance).Should().Be(-1); + + locations1.Insert(0, new ThreadFlowLocation() { ExecutionTimeUtc = DateTime.UtcNow }); + locations2.Insert(0, new ThreadFlowLocation() { ExecutionTimeUtc = DateTime.UtcNow.AddHours(-2) }); + + ComparerHelper.CompareList(locations1, locations2, ThreadFlowLocationComparer.Instance).Should().Be(1); + ComparerHelper.CompareList(locations2, locations1, ThreadFlowLocationComparer.Instance).Should().Be(-1); + + locations1.Insert(0, new ThreadFlowLocation() { Importance = ThreadFlowLocationImportance.Essential }); + locations2.Insert(0, new ThreadFlowLocation() { Importance = ThreadFlowLocationImportance.Unimportant }); + + ComparerHelper.CompareList(locations1, locations2, ThreadFlowLocationComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(locations2, locations1, ThreadFlowLocationComparer.Instance).Should().Be(1); + } + + [Fact] + public void RunComparer_Tests() + { + var runs1 = new List(); + var runs2 = new List(); + + runs1.Add(null); + runs2.Add(null); + ComparerHelper.CompareList(runs1, runs2, RunComparer.Instance).Should().Be(0); + + runs1.Insert(0, new Run() { Artifacts = new Artifact[] { new Artifact { Description = new Message { Text = "artifact 1" } } } }); + runs2.Insert(0, new Run() { Artifacts = new Artifact[] { new Artifact { Description = new Message { Text = "artifact 2" } } } }); + + ComparerHelper.CompareList(runs1, runs2, RunComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(runs2, runs1, RunComparer.Instance).Should().Be(1); + + Tool tool1 = new Tool { Driver = new ToolComponent { Name = "PREFast", Version = "1.0" } }; + Tool tool2 = new Tool { Driver = new ToolComponent { Name = "PREFast", Version = "1.3" } }; + + runs1.Insert(0, new Run() { Tool = tool1 }); + runs2.Insert(0, new Run() { Tool = tool2 }); + + ComparerHelper.CompareList(runs1, runs2, RunComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(runs2, runs1, RunComparer.Instance).Should().Be(1); + + Result result1 = new Result { RuleId = "CS001", Message = new Message { Text = "Issue of C# code" } }; + Result result2 = new Result { RuleId = "IDE692", Message = new Message { Text = "Issue by IDE" } }; + + runs1.Insert(0, new Run() { Results = new Result[] { result1 } }); + runs2.Insert(0, new Run() { Results = new Result[] { result2 } }); + + ComparerHelper.CompareList(runs1, runs2, RunComparer.Instance).Should().Be(-1); + ComparerHelper.CompareList(runs2, runs1, RunComparer.Instance).Should().Be(1); + } + } +} diff --git a/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs b/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs index 0d77102ae..d94cb7422 100644 --- a/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs +++ b/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs @@ -29,7 +29,7 @@ public static Random GenerateRandomAndLog(ITestOutputHelper output, [CallerMembe return random; } - public static SarifLog GenerateSarifLogWithRuns(Random randomGen, int runCount, int? resultCount = null) + public static SarifLog GenerateSarifLogWithRuns(Random randomGen, int runCount, int? resultCount = null, RandomDataFields dataFields = RandomDataFields.None) { SarifLog log = new SarifLog(); @@ -40,13 +40,13 @@ public static SarifLog GenerateSarifLogWithRuns(Random randomGen, int runCount, for (int i = 0; i < runCount; i++) { - log.Runs.Add(GenerateRandomRun(randomGen, resultCount)); + log.Runs.Add(GenerateRandomRun(randomGen, resultCount, dataFields)); } return log; } - public static Run GenerateRandomRun(Random random, int? resultCount = null) + public static Run GenerateRandomRun(Random random, int? resultCount = null, RandomDataFields dataFields = RandomDataFields.None) { List ruleIds = new List() { "TEST001", "TEST002", "TEST003", "TEST004", "TEST005" }; List filePaths = GenerateFakeFiles(GeneratorBaseUri, random.Next(20) + 1).Select(a => new Uri(a)).ToList(); @@ -64,7 +64,7 @@ public static Run GenerateRandomRun(Random random, int? resultCount = null) } }, Artifacts = GenerateFiles(filePaths), - Results = GenerateFakeResults(random, ruleIds, filePaths, results) + Results = GenerateFakeResults(random, ruleIds, filePaths, results, dataFields) }; } @@ -96,7 +96,7 @@ public static IEnumerable GenerateFakeFiles(string baseAddress, int coun return results; } - public static IList GenerateFakeResults(Random random, List ruleIds, List filePaths, int resultCount) + public static IList GenerateFakeResults(Random random, List ruleIds, List filePaths, int resultCount, RandomDataFields dataFields = RandomDataFields.None) { List results = new List(); for (int i = 0; i < resultCount; i++) @@ -118,9 +118,15 @@ public static IList GenerateFakeResults(Random random, List rule Uri = filePaths[fileIndex], Index = fileIndex }, - } + }, + LogicalLocations = dataFields.HasFlag(RandomDataFields.LogicalLocation) ? + GenerateLogicalLocations(random) : + null, } - } + }, + CodeFlows = dataFields.HasFlag(RandomDataFields.CodeFlow) ? + GenerateCodeFlows(random, filePaths, dataFields) : + null, }); } return results; @@ -161,5 +167,92 @@ public static IList GenerateRules(List ruleIds) } return rules; } + + public static IList GenerateCodeFlows(Random random, IList artifacts, RandomDataFields dataFields) + { + if (artifacts?.Any() != true) + { + return null; + } + + var codeFlow = new CodeFlow + { + Message = new Message { Text = "code flow message" }, + ThreadFlows = new[] + { + new ThreadFlow + { + Message = new Message { Text = "thread flow message" }, + Locations = dataFields.HasFlag(RandomDataFields.ThreadFlow) ? + GenerateThreadFlowLocations(random, artifacts) : + null, + }, + }, + }; + + return new[] { codeFlow }; + } + + public static IList GenerateThreadFlowLocations(Random random, IList artifacts) + { + var locations = new List(); + + for (int i = 0; i < random.Next(10); i++) + { + locations.Add(new ThreadFlowLocation + { + Importance = RandomEnumValue(random), + Location = new Location + { + PhysicalLocation = new PhysicalLocation + { + ArtifactLocation = new ArtifactLocation + { + Index = random.Next(artifacts.Count), + }, + Region = new Region + { + StartLine = random.Next(500), + StartColumn = random.Next(100), + }, + }, + }, + }); + } + + return locations; + } + + public static IList GenerateLogicalLocations(Random random) + { + var logicalLocations = new List(); + for (int i = 0; i < random.Next(5); i++) + { + logicalLocations.Add(new LogicalLocation + { + Name = $"Class{i}", + Index = i, + FullyQualifiedName = "namespaceA::namespaceB::namespaceC", + Kind = LogicalLocationKind.Type, + }); + } + + return logicalLocations; + } + + public static T RandomEnumValue(Random random) where T : Enum + { + Array enums = Enum.GetValues(typeof(T)); + return (T)enums.GetValue(random.Next(enums.Length)); + } + } + + [Flags] + public enum RandomDataFields + { + None = 0, + CodeFlow = 0b1, + ThreadFlow = 0b10, + LogicalLocation = 0b100, } } diff --git a/src/Test.UnitTests.Sarif/Visitors/SortingVisitorTests.cs b/src/Test.UnitTests.Sarif/Visitors/SortingVisitorTests.cs index 6a5015c96..ea9163ff7 100644 --- a/src/Test.UnitTests.Sarif/Visitors/SortingVisitorTests.cs +++ b/src/Test.UnitTests.Sarif/Visitors/SortingVisitorTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Linq; using FluentAssertions; @@ -22,34 +23,19 @@ public class SortingVisitorTests public SortingVisitorTests(ITestOutputHelper outputHelper) { - random = RandomSarifLogGenerator.GenerateRandomAndLog(outputHelper); + this.random = RandomSarifLogGenerator.GenerateRandomAndLog(outputHelper); } [Fact] public void SortingVisitor_ShuffleTest() { bool areEqual; - // create a test sarif log - SarifLog originalLog = CreateTestSarifLog(this.random); - - SarifLog shuffledLog1 = originalLog.DeepClone(); - SarifLog shuffledLog2 = originalLog.DeepClone(); - - // original log and cloned log should be same - areEqual = SarifLogEqualityComparer.Instance.Equals(originalLog, shuffledLog1); - areEqual.Should().BeTrue(); - - areEqual = SarifLogEqualityComparer.Instance.Equals(originalLog, shuffledLog2); - areEqual.Should().BeTrue(); - - areEqual = SarifLogEqualityComparer.Instance.Equals(shuffledLog1, shuffledLog2); - areEqual.Should().BeTrue(); - // shuffle sarif log - ShuffleSarifLog(shuffledLog1, this.random); - ShuffleSarifLog(shuffledLog2, this.random); + SarifLog originalLog = CreateTestSarifLog(this.random); + SarifLog shuffledLog1 = ShuffleSarifLog(originalLog, this.random); + SarifLog shuffledLog2 = ShuffleSarifLog(originalLog, this.random); - // shuffled logs should be not same as each other and original log. + // Shuffled logs should be not same as each other and original log. areEqual = SarifLogEqualityComparer.Instance.Equals(originalLog, shuffledLog1); areEqual.Should().BeFalse(); @@ -59,16 +45,13 @@ public void SortingVisitor_ShuffleTest() areEqual = SarifLogEqualityComparer.Instance.Equals(shuffledLog1, shuffledLog2); areEqual.Should().BeFalse(); - // sort shuffled logs using visitor SarifLog sortedLog1 = new SortingVisitor().VisitSarifLog(shuffledLog1); SarifLog sortedLog2 = new SortingVisitor().VisitSarifLog(shuffledLog2); - // verify sorted logs should be same (deterministic) - // sorted logs may not be same with original log due to random values areEqual = SarifLogEqualityComparer.Instance.Equals(sortedLog1, sortedLog2); areEqual.Should().BeTrue(); - // make sure result's ruleIndex points to right rule in sorted log + // Make sure result's ruleIndex points to right rule in sorted log. IList rules = sortedLog1.Runs.First().Tool.Driver.Rules; foreach (Result result in sortedLog1.Runs.First().Results.Where(r => r.RuleIndex != -1)) { @@ -76,7 +59,7 @@ public void SortingVisitor_ShuffleTest() result.RuleIndex.Should().Be(ruleIndex); } - // make sure artifactLocation index points to right artifacts + // Make sure artifactLocation index points to right artifacts. IList artifacts = sortedLog1.Runs.First().Artifacts; foreach (Result result in sortedLog1.Runs.First().Results) { @@ -91,9 +74,8 @@ public void SortingVisitor_ShuffleTest() [Fact] public void SortingVisitor_NullEmptyListTests() { - // arrange - // create a log with all results have same value. - SarifLog sarifLog = new SarifLog + // Create a log with all results have same values. + var sarifLog = new SarifLog { Runs = new[] { @@ -118,19 +100,16 @@ public void SortingVisitor_NullEmptyListTests() }, }; - // setup results IList results = sarifLog.Runs[0].Results; results[0].Locations = new[] { new Location { Message = new Message { Text = "test location" } } }; results[1].Locations = null; results[2].Locations = new List(); - // act SarifLog sortedLog = new SortingVisitor().VisitSarifLog(sarifLog); - // assert - // if collection elements all values are same expect a child list + // If sorting a collection with element has a list type property // the order should depend on list values. - // expected order: null < empty < collection has element + // Expected order: null < empty < collection has element. results = sortedLog.Runs[0].Results; results[0].Locations.Should().BeNull(); results[1].Locations.Should().BeEmpty(); @@ -139,168 +118,116 @@ public void SortingVisitor_NullEmptyListTests() private static SarifLog CreateTestSarifLog(Random random) { - SarifLog sarifLog = RandomSarifLogGenerator.GenerateSarifLogWithRuns(random, runCount: 1, resultCount: random.Next(100)); - - CreateCodeFlows(sarifLog.Runs?.First()?.Results, sarifLog.Runs?.First()?.Artifacts, random); + SarifLog sarifLog = RandomSarifLogGenerator.GenerateSarifLogWithRuns( + randomGen: random, + runCount: random.Next(1, 5), + dataFields: RandomDataFields.CodeFlow | RandomDataFields.ThreadFlow | RandomDataFields.LogicalLocation); return sarifLog; } - private static void CreateCodeFlows(IList results, IList artifacts, Random random) + private static SarifLog ShuffleSarifLog(SarifLog originalLog, Random random) { - if (results?.Any() != true || artifacts?.Any() != true) - { - return; - } + SarifLog logToBeShuffled = originalLog.DeepClone(); - foreach (Result result in results) + bool areEqual = SarifLogEqualityComparer.Instance.Equals(originalLog, logToBeShuffled); + areEqual.Should().BeTrue(); + + // Shuffle the log cloned from original log until + // find the log is different than original log. + do { - result.CodeFlows = new[] + foreach (Run run in logToBeShuffled?.Runs) { - new CodeFlow + IList rules = run?.Tool?.Driver?.Rules; + IList results = run?.Results; + IList artifacts = run?.Artifacts; + + if (rules != null) { - ThreadFlows = new[] + IDictionary ruleIndexMapping = new Dictionary(); + + rules = rules.Shuffle(random); + run.Tool.Driver.Rules = rules; + + for (int i = 0; i < rules.Count; i++) { - new ThreadFlow + ruleIndexMapping.Add(rules[i].Id, i); + } + + foreach (Result result in results.Where(r => r.RuleIndex != -1)) + { + if (ruleIndexMapping.TryGetValue(result.RuleId, out int newIndex)) { - Locations = GenerateRandomThreadFlowLocations( - count: random.Next(10), artifacts, random) + result.RuleIndex = newIndex; } } } - }; - } - } - private static IList GenerateRandomThreadFlowLocations(int count, IList artifacts, Random random) - { - var locations = new List(); - - for (int i = 0; i < count; i++) - { - locations.Add(new ThreadFlowLocation - { - Importance = RandomEnumValue(random), - Location = new Location + if (artifacts != null) { - PhysicalLocation = new PhysicalLocation - { - ArtifactLocation = new ArtifactLocation - { - Index = random.Next(artifacts.Count), - }, - Region = new Region - { - StartLine = random.Next(500), - StartColumn = random.Next(100), - }, - }, - }, - }); - } - return locations; - } + IDictionary artifactIndexMapping = new Dictionary(); - private static T RandomEnumValue(Random random) where T : Enum - { - Array enums = Enum.GetValues(typeof(T)); - return (T)enums.GetValue(random.Next(enums.Length)); - } + IDictionary oldMapping = new Dictionary(); + for (int i = 0; i < artifacts.Count; i++) + { + oldMapping.Add(artifacts[i], i); + } - private static void ShuffleSarifLog(SarifLog log, Random random) - { - Run run = log?.Runs.First(); - IList rules = run?.Tool?.Driver?.Rules; - IList results = run?.Results; - IList artifacts = run?.Artifacts; + artifacts = artifacts.Shuffle(random); + run.Artifacts = artifacts; - if (rules != null) - { - IDictionary ruleIndexMapping = new Dictionary(); + for (int i = 0; i < artifacts.Count; i++) + { + if (oldMapping.TryGetValue(artifacts[i], out int oldIndex)) + { + artifactIndexMapping.Add(oldIndex, i); + } + } - // shuffle rules - rules = rules.Shuffle(random); - log.Runs.First().Tool.Driver.Rules = rules; + var locToUpdate = new List(); + locToUpdate.AddRange( + results + .SelectMany(r => r.Locations) + .Select(l => l.PhysicalLocation.ArtifactLocation)); - // store new rules indexes - for (int i = 0; i < rules.Count; i++) - { - ruleIndexMapping.Add(rules[i].Id, i); - } + locToUpdate.AddRange( + results + .SelectMany(r => r.CodeFlows) + .SelectMany(c => c.ThreadFlows) + .SelectMany(t => t.Locations) + .Select(l => l.Location.PhysicalLocation.ArtifactLocation)); - // update results rule index - foreach (Result result in results.Where(r => r.RuleIndex != -1)) - { - if (ruleIndexMapping.TryGetValue(result.RuleId, out int newIndex)) - { - result.RuleIndex = newIndex; + foreach (ArtifactLocation artifactLocation in locToUpdate.Where(l => l.Index != -1)) + { + if (artifactIndexMapping.TryGetValue(artifactLocation.Index, out int newIndex)) + { + artifactLocation.Index = newIndex; + } + } } - } - } - if (artifacts != null) - { - IDictionary artifactIndexMapping = new Dictionary(); - // store old artifacts indexes - IDictionary oldMapping = new Dictionary(); - for (int i = 0; i < artifacts.Count; i++) - { - oldMapping.Add(artifacts[i], i); - } - - // shuffle artifacts - artifacts = artifacts.Shuffle(random); - log.Runs.First().Artifacts = artifacts; - - // store new artifacts indexes - for (int i = 0; i < artifacts.Count; i++) - { - if (oldMapping.TryGetValue(artifacts[i], out int oldIndex)) - { - artifactIndexMapping.Add(oldIndex, i); - } - } + run.Results = results.Shuffle(random); - // update all artifacts' index if its set. - List locToUpdate = new List(); - locToUpdate.AddRange( - results - .SelectMany(r => r.Locations) - .Select(l => l.PhysicalLocation.ArtifactLocation)); - - locToUpdate.AddRange( - results - .SelectMany(r => r.CodeFlows) - .SelectMany(c => c.ThreadFlows) - .SelectMany(t => t.Locations) - .Select(l => l.Location.PhysicalLocation.ArtifactLocation)); - - foreach (ArtifactLocation artifactLocation in locToUpdate.Where(l => l.Index != -1)) - { - if (artifactIndexMapping.TryGetValue(artifactLocation.Index, out int newIndex)) + foreach (Result result in run.Results) { - artifactLocation.Index = newIndex; + result.CodeFlows = result.CodeFlows.Shuffle(random); + foreach (CodeFlow codeFlow in result.CodeFlows) + { + codeFlow.ThreadFlows = codeFlow.ThreadFlows.Shuffle(random); + foreach (ThreadFlow threadFlow in codeFlow.ThreadFlows) + { + threadFlow.Locations = threadFlow.Locations.Shuffle(random); + } + } } } + logToBeShuffled.Runs = logToBeShuffled.Runs.Shuffle(random); } + while (SarifLogEqualityComparer.Instance.Equals(logToBeShuffled, originalLog)); - // shuffle results - log.Runs.First().Results = results.Shuffle(random); - - // shuffle codeflow locations - foreach (Result result in log.Runs.First().Results) - { - result.CodeFlows = result.CodeFlows.Shuffle(random); - foreach (CodeFlow codeFlow in result.CodeFlows) - { - codeFlow.ThreadFlows = codeFlow.ThreadFlows.Shuffle(random); - foreach (ThreadFlow threadFlow in codeFlow.ThreadFlows) - { - threadFlow.Locations = threadFlow.Locations.Shuffle(random); - } - } - } + return logToBeShuffled; } } } diff --git a/src/Test.Utilities.Sarif/TestUtilitiesExtensions.cs b/src/Test.Utilities.Sarif/TestUtilitiesExtensions.cs index 26fe16014..849a2d9f4 100644 --- a/src/Test.Utilities.Sarif/TestUtilitiesExtensions.cs +++ b/src/Test.Utilities.Sarif/TestUtilitiesExtensions.cs @@ -37,14 +37,19 @@ public static TestRuleBehaviors AccessibleWithinContextOnly(this TestRuleBehavio return behaviors & ~behaviors.AccessibleOutsideOfContextOnly(); } - public static IList Shuffle(this IList list, Random random = null) + public static IList Shuffle(this IList list, Random random) { if (list == null) { return null; } - random ??= new Random(); + if (random == null) + { + // Random object with seed logged in test is required. + throw new ArgumentNullException(nameof(random)); + } + return list.OrderBy(item => random.Next()).ToList(); } }