sarif-ruby 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +10 -0
  4. data/LICENSE +21 -0
  5. data/README.md +191 -0
  6. data/Rakefile +10 -0
  7. data/lib/sarif/address.rb +67 -0
  8. data/lib/sarif/artifact.rb +76 -0
  9. data/lib/sarif/artifact_change.rb +46 -0
  10. data/lib/sarif/artifact_content.rb +49 -0
  11. data/lib/sarif/artifact_location.rb +52 -0
  12. data/lib/sarif/attachment.rb +52 -0
  13. data/lib/sarif/code_flow.rb +46 -0
  14. data/lib/sarif/configuration_override.rb +46 -0
  15. data/lib/sarif/conversion.rb +49 -0
  16. data/lib/sarif/edge.rb +52 -0
  17. data/lib/sarif/edge_traversal.rb +52 -0
  18. data/lib/sarif/exception.rb +52 -0
  19. data/lib/sarif/external_properties.rb +100 -0
  20. data/lib/sarif/external_property_file_reference.rb +49 -0
  21. data/lib/sarif/external_property_file_references.rb +88 -0
  22. data/lib/sarif/fix.rb +46 -0
  23. data/lib/sarif/graph.rb +49 -0
  24. data/lib/sarif/graph_traversal.rb +58 -0
  25. data/lib/sarif/invocation.rb +115 -0
  26. data/lib/sarif/location.rb +58 -0
  27. data/lib/sarif/location_relationship.rb +49 -0
  28. data/lib/sarif/log.rb +52 -0
  29. data/lib/sarif/logical_location.rb +58 -0
  30. data/lib/sarif/message.rb +52 -0
  31. data/lib/sarif/multiformat_message_string.rb +46 -0
  32. data/lib/sarif/node.rb +52 -0
  33. data/lib/sarif/notification.rb +64 -0
  34. data/lib/sarif/physical_location.rb +52 -0
  35. data/lib/sarif/property_bag.rb +40 -0
  36. data/lib/sarif/rectangle.rb +55 -0
  37. data/lib/sarif/region.rb +73 -0
  38. data/lib/sarif/replacement.rb +46 -0
  39. data/lib/sarif/reporting_configuration.rb +52 -0
  40. data/lib/sarif/reporting_descriptor.rb +79 -0
  41. data/lib/sarif/reporting_descriptor_reference.rb +52 -0
  42. data/lib/sarif/reporting_descriptor_relationship.rb +49 -0
  43. data/lib/sarif/result.rb +127 -0
  44. data/lib/sarif/result_provenance.rb +58 -0
  45. data/lib/sarif/run.rb +121 -0
  46. data/lib/sarif/run_automation_details.rb +52 -0
  47. data/lib/sarif/schema/sarif-schema-2.1.0.json +3389 -0
  48. data/lib/sarif/special_locations.rb +43 -0
  49. data/lib/sarif/stack.rb +46 -0
  50. data/lib/sarif/stack_frame.rb +52 -0
  51. data/lib/sarif/suppression.rb +55 -0
  52. data/lib/sarif/thread_flow.rb +55 -0
  53. data/lib/sarif/thread_flow_location.rb +79 -0
  54. data/lib/sarif/tool.rb +46 -0
  55. data/lib/sarif/tool_component.rb +121 -0
  56. data/lib/sarif/tool_component_reference.rb +49 -0
  57. data/lib/sarif/translation_metadata.rb +58 -0
  58. data/lib/sarif/version.rb +5 -0
  59. data/lib/sarif/version_control_details.rb +58 -0
  60. data/lib/sarif/web_request.rb +64 -0
  61. data/lib/sarif/web_response.rb +64 -0
  62. data/lib/sarif.rb +121 -0
  63. data/sig/sarif.rbs +4 -0
  64. metadata +106 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 79bce3486dd120e26e0263dcb1bb6b2a9d927c5e75390563eaa78c08cab73065
4
+ data.tar.gz: a8265311c18e62d73ed428b75f7a019355a2122566569e67f8095aa3b3322df0
5
+ SHA512:
6
+ metadata.gz: acaaf4c0aee7cbbb54efa9eb7a77d8d2ecf5a033fde1fa7a7606dc1b6559b38d4bf745f38349a5c7e9c0f36ebcbf84bc9dfd84a0ef681f155775be426b8a11f2
7
+ data.tar.gz: 12337301a09f56547993e8468d632b6b1d9816e58e9cf0378b5bf5eb2045477dc2e7e24672ff2fa681561d5980b096b6ca141d5136827cbd26ce3e45707774f8
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2026-01-07
4
+
5
+ - Initial release
@@ -0,0 +1,10 @@
1
+ # Code of Conduct
2
+
3
+ "sarif" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
4
+
5
+ * Participants will be tolerant of opposing views.
6
+ * Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
7
+ * When interpreting the words and actions of others, participants should always assume good intentions.
8
+ * Behaviour which can be reasonably considered harassment will not be tolerated.
9
+
10
+ If you have any concerns about behaviour within this project, please contact us at ["andrewnez@gmail.com"](mailto:"andrewnez@gmail.com").
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Andrew Nesbitt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # sarif-ruby
2
+
3
+ A Ruby SDK for SARIF (Static Analysis Results Interchange Format) 2.1.0.
4
+
5
+ SARIF is an OASIS standard format for representing static analysis tool output. This gem provides Ruby classes for creating, reading, and manipulating SARIF files.
6
+
7
+ ## Installation
8
+
9
+ Add to your Gemfile:
10
+
11
+ ```ruby
12
+ gem "sarif-ruby"
13
+ ```
14
+
15
+ Or install directly:
16
+
17
+ ```
18
+ gem install sarif-ruby
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ### Creating SARIF output
24
+
25
+ ```ruby
26
+ require "sarif"
27
+
28
+ log = Sarif::Log.new(
29
+ version: "2.1.0",
30
+ runs: [
31
+ Sarif::Run.new(
32
+ tool: Sarif::Tool.new(
33
+ driver: Sarif::ToolComponent.new(
34
+ name: "my-linter",
35
+ version: "1.0.0",
36
+ information_uri: "https://example.com/my-linter"
37
+ )
38
+ ),
39
+ results: [
40
+ Sarif::Result.new(
41
+ rule_id: "no-unused-vars",
42
+ level: "warning",
43
+ message: Sarif::Message.new(text: "Variable 'x' is unused"),
44
+ locations: [
45
+ Sarif::Location.new(
46
+ physical_location: Sarif::PhysicalLocation.new(
47
+ artifact_location: Sarif::ArtifactLocation.new(uri: "src/main.rb"),
48
+ region: Sarif::Region.new(start_line: 10, start_column: 5)
49
+ )
50
+ )
51
+ ]
52
+ )
53
+ ]
54
+ )
55
+ ]
56
+ )
57
+
58
+ # Write to file
59
+ Sarif.dump(log, "results.sarif")
60
+
61
+ # Write pretty-printed JSON
62
+ Sarif.dump(log, "results.sarif", pretty: true)
63
+
64
+ # Get JSON string
65
+ json = log.to_json(pretty: true)
66
+ ```
67
+
68
+ ### Reading SARIF files
69
+
70
+ ```ruby
71
+ # Load from file
72
+ log = Sarif.load("results.sarif")
73
+
74
+ # Parse JSON string
75
+ log = Sarif.parse(json_string)
76
+
77
+ # Access data
78
+ log.runs.each do |run|
79
+ puts "Tool: #{run.tool.driver.name}"
80
+
81
+ run.results&.each do |result|
82
+ puts " #{result.rule_id}: #{result.message.text}"
83
+
84
+ result.locations&.each do |location|
85
+ loc = location.physical_location
86
+ puts " #{loc.artifact_location.uri}:#{loc.region&.start_line}"
87
+ end
88
+ end
89
+ end
90
+ ```
91
+
92
+ ### Defining rules
93
+
94
+ ```ruby
95
+ Sarif::Run.new(
96
+ tool: Sarif::Tool.new(
97
+ driver: Sarif::ToolComponent.new(
98
+ name: "my-linter",
99
+ version: "1.0.0",
100
+ rules: [
101
+ Sarif::ReportingDescriptor.new(
102
+ id: "no-unused-vars",
103
+ name: "NoUnusedVariables",
104
+ short_description: Sarif::MultiformatMessageString.new(
105
+ text: "Disallow unused variables"
106
+ ),
107
+ full_description: Sarif::MultiformatMessageString.new(
108
+ text: "Variables that are declared but never used are likely mistakes."
109
+ ),
110
+ default_configuration: Sarif::ReportingConfiguration.new(
111
+ level: "warning"
112
+ ),
113
+ help_uri: "https://example.com/rules/no-unused-vars"
114
+ )
115
+ ]
116
+ )
117
+ ),
118
+ results: [
119
+ Sarif::Result.new(
120
+ rule_id: "no-unused-vars",
121
+ rule_index: 0,
122
+ message: Sarif::Message.new(text: "Variable 'x' is unused")
123
+ )
124
+ ]
125
+ )
126
+ ```
127
+
128
+ ### Result levels
129
+
130
+ SARIF defines four severity levels:
131
+
132
+ - `"error"` - A serious problem
133
+ - `"warning"` - A potential problem (default)
134
+ - `"note"` - Informational finding
135
+ - `"none"` - No severity
136
+
137
+ ```ruby
138
+ Sarif::Result.new(
139
+ rule_id: "security-issue",
140
+ level: "error",
141
+ message: Sarif::Message.new(text: "SQL injection vulnerability")
142
+ )
143
+ ```
144
+
145
+ ## Available classes
146
+
147
+ The gem provides classes for all SARIF 2.1.0 types:
148
+
149
+ | Class | Description |
150
+ |-------|-------------|
151
+ | `Sarif::Log` | Root object containing runs |
152
+ | `Sarif::Run` | Single tool execution |
153
+ | `Sarif::Tool` | Tool metadata |
154
+ | `Sarif::ToolComponent` | Tool driver or extension |
155
+ | `Sarif::Result` | Individual finding |
156
+ | `Sarif::Message` | Human-readable message |
157
+ | `Sarif::Location` | Where a result was detected |
158
+ | `Sarif::PhysicalLocation` | File and region |
159
+ | `Sarif::ArtifactLocation` | File path or URI |
160
+ | `Sarif::Region` | Line/column range |
161
+ | `Sarif::ReportingDescriptor` | Rule definition |
162
+ | `Sarif::ReportingConfiguration` | Rule configuration |
163
+ | `Sarif::Fix` | Proposed fix |
164
+ | `Sarif::Invocation` | Tool execution details |
165
+ | ... | And 40+ more |
166
+
167
+ ## Regenerating classes
168
+
169
+ Classes are generated from the official SARIF JSON schema. To regenerate:
170
+
171
+ ```
172
+ bundle exec rake sarif:generate
173
+ ```
174
+
175
+ ## Links
176
+
177
+ - [SARIF Specification](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html)
178
+ - [SARIF JSON Schema](https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json)
179
+ - [SARIF Tutorials](https://github.com/microsoft/sarif-tutorials)
180
+ - [OASIS SARIF TC](https://www.oasis-open.org/committees/sarif/)
181
+
182
+ ### Other SARIF SDKs
183
+
184
+ - [sarif-python-om](https://github.com/microsoft/sarif-python-om) - Python
185
+ - [java-sarif](https://github.com/Contrast-Security-OSS/java-sarif) - Java
186
+ - [sarif-sdk](https://github.com/microsoft/sarif-sdk) - .NET
187
+ - [sarif-js-sdk](https://github.com/microsoft/sarif-js-sdk) - JavaScript
188
+
189
+ ## License
190
+
191
+ MIT License. See [LICENSE](LICENSE) for details.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Dir.glob("tasks/**/*.rake").each { |r| load r }
7
+
8
+ Minitest::TestTask.create
9
+
10
+ task default: :test
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sarif
4
+ # A physical or virtual address, or a range of addresses, in an 'addressable region' (memory or a binary file).
5
+ class Address
6
+ attr_accessor :absolute_address, :relative_address, :length, :kind, :name, :fully_qualified_name, :offset_from_parent, :index, :parent_index, :properties
7
+
8
+ def initialize(absolute_address: -1, relative_address: nil, length: nil, kind: nil, name: nil, fully_qualified_name: nil, offset_from_parent: nil, index: -1, parent_index: -1, properties: nil)
9
+ @absolute_address = absolute_address
10
+ @relative_address = relative_address
11
+ @length = length
12
+ @kind = kind
13
+ @name = name
14
+ @fully_qualified_name = fully_qualified_name
15
+ @offset_from_parent = offset_from_parent
16
+ @index = index
17
+ @parent_index = parent_index
18
+ @properties = properties
19
+ end
20
+
21
+ def to_h
22
+ h = {}
23
+ h["absoluteAddress"] = @absolute_address if @absolute_address && @absolute_address != -1
24
+ h["relativeAddress"] = @relative_address unless @relative_address.nil?
25
+ h["length"] = @length unless @length.nil?
26
+ h["kind"] = @kind unless @kind.nil?
27
+ h["name"] = @name unless @name.nil?
28
+ h["fullyQualifiedName"] = @fully_qualified_name unless @fully_qualified_name.nil?
29
+ h["offsetFromParent"] = @offset_from_parent unless @offset_from_parent.nil?
30
+ h["index"] = @index if @index && @index != -1
31
+ h["parentIndex"] = @parent_index if @parent_index && @parent_index != -1
32
+ h["properties"] = @properties unless @properties.nil?
33
+ h
34
+ end
35
+
36
+ def to_json(pretty: false)
37
+ pretty ? JSON.pretty_generate(to_h) : JSON.generate(to_h)
38
+ end
39
+
40
+ def self.from_hash(h)
41
+ return nil if h.nil?
42
+ new(
43
+ absolute_address: h["absoluteAddress"] || -1,
44
+ relative_address: h["relativeAddress"],
45
+ length: h["length"],
46
+ kind: h["kind"],
47
+ name: h["name"],
48
+ fully_qualified_name: h["fullyQualifiedName"],
49
+ offset_from_parent: h["offsetFromParent"],
50
+ index: h["index"] || -1,
51
+ parent_index: h["parentIndex"] || -1,
52
+ properties: h["properties"]
53
+ )
54
+ end
55
+
56
+ def ==(other)
57
+ return false unless other.is_a?(Address)
58
+ @absolute_address == other.absolute_address && @relative_address == other.relative_address && @length == other.length && @kind == other.kind && @name == other.name && @fully_qualified_name == other.fully_qualified_name && @offset_from_parent == other.offset_from_parent && @index == other.index && @parent_index == other.parent_index && @properties == other.properties
59
+ end
60
+
61
+ alias eql? ==
62
+
63
+ def hash
64
+ [@absolute_address, @relative_address, @length, @kind, @name, @fully_qualified_name, @offset_from_parent, @index, @parent_index, @properties].hash
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sarif
4
+ # A single artifact. In some cases, this artifact might be nested within another artifact.
5
+ class Artifact
6
+ attr_accessor :description, :location, :parent_index, :offset, :length, :roles, :mime_type, :contents, :encoding, :source_language, :hashes, :last_modified_time_utc, :properties
7
+
8
+ def initialize(description: nil, location: nil, parent_index: -1, offset: nil, length: -1, roles: [], mime_type: nil, contents: nil, encoding: nil, source_language: nil, hashes: nil, last_modified_time_utc: nil, properties: nil)
9
+ @description = description
10
+ @location = location
11
+ @parent_index = parent_index
12
+ @offset = offset
13
+ @length = length
14
+ @roles = roles
15
+ @mime_type = mime_type
16
+ @contents = contents
17
+ @encoding = encoding
18
+ @source_language = source_language
19
+ @hashes = hashes
20
+ @last_modified_time_utc = last_modified_time_utc
21
+ @properties = properties
22
+ end
23
+
24
+ def to_h
25
+ h = {}
26
+ h["description"] = @description&.to_h unless @description.nil?
27
+ h["location"] = @location&.to_h unless @location.nil?
28
+ h["parentIndex"] = @parent_index if @parent_index && @parent_index != -1
29
+ h["offset"] = @offset unless @offset.nil?
30
+ h["length"] = @length if @length && @length != -1
31
+ h["roles"] = @roles&.map(&:to_s) if @roles&.any?
32
+ h["mimeType"] = @mime_type unless @mime_type.nil?
33
+ h["contents"] = @contents&.to_h unless @contents.nil?
34
+ h["encoding"] = @encoding unless @encoding.nil?
35
+ h["sourceLanguage"] = @source_language unless @source_language.nil?
36
+ h["hashes"] = @hashes unless @hashes.nil?
37
+ h["lastModifiedTimeUtc"] = @last_modified_time_utc unless @last_modified_time_utc.nil?
38
+ h["properties"] = @properties unless @properties.nil?
39
+ h
40
+ end
41
+
42
+ def to_json(pretty: false)
43
+ pretty ? JSON.pretty_generate(to_h) : JSON.generate(to_h)
44
+ end
45
+
46
+ def self.from_hash(h)
47
+ return nil if h.nil?
48
+ new(
49
+ description: Message.from_hash(h["description"]),
50
+ location: ArtifactLocation.from_hash(h["location"]),
51
+ parent_index: h["parentIndex"] || -1,
52
+ offset: h["offset"],
53
+ length: h["length"] || -1,
54
+ roles: h["roles"] || [],
55
+ mime_type: h["mimeType"],
56
+ contents: ArtifactContent.from_hash(h["contents"]),
57
+ encoding: h["encoding"],
58
+ source_language: h["sourceLanguage"],
59
+ hashes: h["hashes"],
60
+ last_modified_time_utc: h["lastModifiedTimeUtc"],
61
+ properties: h["properties"]
62
+ )
63
+ end
64
+
65
+ def ==(other)
66
+ return false unless other.is_a?(Artifact)
67
+ @description == other.description && @location == other.location && @parent_index == other.parent_index && @offset == other.offset && @length == other.length && @roles == other.roles && @mime_type == other.mime_type && @contents == other.contents && @encoding == other.encoding && @source_language == other.source_language && @hashes == other.hashes && @last_modified_time_utc == other.last_modified_time_utc && @properties == other.properties
68
+ end
69
+
70
+ alias eql? ==
71
+
72
+ def hash
73
+ [@description, @location, @parent_index, @offset, @length, @roles, @mime_type, @contents, @encoding, @source_language, @hashes, @last_modified_time_utc, @properties].hash
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sarif
4
+ # A change to a single artifact.
5
+ class ArtifactChange
6
+ attr_accessor :artifact_location, :replacements, :properties
7
+
8
+ def initialize(artifact_location:, replacements:, properties: nil)
9
+ @artifact_location = artifact_location
10
+ @replacements = replacements
11
+ @properties = properties
12
+ end
13
+
14
+ def to_h
15
+ h = {}
16
+ h["artifactLocation"] = @artifact_location&.to_h
17
+ h["replacements"] = @replacements&.map(&:to_h)
18
+ h["properties"] = @properties unless @properties.nil?
19
+ h
20
+ end
21
+
22
+ def to_json(pretty: false)
23
+ pretty ? JSON.pretty_generate(to_h) : JSON.generate(to_h)
24
+ end
25
+
26
+ def self.from_hash(h)
27
+ return nil if h.nil?
28
+ new(
29
+ artifact_location: ArtifactLocation.from_hash(h["artifactLocation"]),
30
+ replacements: h["replacements"]&.map { |v| Replacement.from_hash(v) },
31
+ properties: h["properties"]
32
+ )
33
+ end
34
+
35
+ def ==(other)
36
+ return false unless other.is_a?(ArtifactChange)
37
+ @artifact_location == other.artifact_location && @replacements == other.replacements && @properties == other.properties
38
+ end
39
+
40
+ alias eql? ==
41
+
42
+ def hash
43
+ [@artifact_location, @replacements, @properties].hash
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sarif
4
+ # Represents the contents of an artifact.
5
+ class ArtifactContent
6
+ attr_accessor :text, :binary, :rendered, :properties
7
+
8
+ def initialize(text: nil, binary: nil, rendered: nil, properties: nil)
9
+ @text = text
10
+ @binary = binary
11
+ @rendered = rendered
12
+ @properties = properties
13
+ end
14
+
15
+ def to_h
16
+ h = {}
17
+ h["text"] = @text unless @text.nil?
18
+ h["binary"] = @binary unless @binary.nil?
19
+ h["rendered"] = @rendered&.to_h unless @rendered.nil?
20
+ h["properties"] = @properties unless @properties.nil?
21
+ h
22
+ end
23
+
24
+ def to_json(pretty: false)
25
+ pretty ? JSON.pretty_generate(to_h) : JSON.generate(to_h)
26
+ end
27
+
28
+ def self.from_hash(h)
29
+ return nil if h.nil?
30
+ new(
31
+ text: h["text"],
32
+ binary: h["binary"],
33
+ rendered: MultiformatMessageString.from_hash(h["rendered"]),
34
+ properties: h["properties"]
35
+ )
36
+ end
37
+
38
+ def ==(other)
39
+ return false unless other.is_a?(ArtifactContent)
40
+ @text == other.text && @binary == other.binary && @rendered == other.rendered && @properties == other.properties
41
+ end
42
+
43
+ alias eql? ==
44
+
45
+ def hash
46
+ [@text, @binary, @rendered, @properties].hash
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sarif
4
+ # Specifies the location of an artifact.
5
+ class ArtifactLocation
6
+ attr_accessor :uri, :uri_base_id, :index, :description, :properties
7
+
8
+ def initialize(uri: nil, uri_base_id: nil, index: -1, description: nil, properties: nil)
9
+ @uri = uri
10
+ @uri_base_id = uri_base_id
11
+ @index = index
12
+ @description = description
13
+ @properties = properties
14
+ end
15
+
16
+ def to_h
17
+ h = {}
18
+ h["uri"] = @uri unless @uri.nil?
19
+ h["uriBaseId"] = @uri_base_id unless @uri_base_id.nil?
20
+ h["index"] = @index if @index && @index != -1
21
+ h["description"] = @description&.to_h unless @description.nil?
22
+ h["properties"] = @properties unless @properties.nil?
23
+ h
24
+ end
25
+
26
+ def to_json(pretty: false)
27
+ pretty ? JSON.pretty_generate(to_h) : JSON.generate(to_h)
28
+ end
29
+
30
+ def self.from_hash(h)
31
+ return nil if h.nil?
32
+ new(
33
+ uri: h["uri"],
34
+ uri_base_id: h["uriBaseId"],
35
+ index: h["index"] || -1,
36
+ description: Message.from_hash(h["description"]),
37
+ properties: h["properties"]
38
+ )
39
+ end
40
+
41
+ def ==(other)
42
+ return false unless other.is_a?(ArtifactLocation)
43
+ @uri == other.uri && @uri_base_id == other.uri_base_id && @index == other.index && @description == other.description && @properties == other.properties
44
+ end
45
+
46
+ alias eql? ==
47
+
48
+ def hash
49
+ [@uri, @uri_base_id, @index, @description, @properties].hash
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sarif
4
+ # An artifact relevant to a result.
5
+ class Attachment
6
+ attr_accessor :description, :artifact_location, :regions, :rectangles, :properties
7
+
8
+ def initialize(description: nil, artifact_location:, regions: [], rectangles: [], properties: nil)
9
+ @description = description
10
+ @artifact_location = artifact_location
11
+ @regions = regions
12
+ @rectangles = rectangles
13
+ @properties = properties
14
+ end
15
+
16
+ def to_h
17
+ h = {}
18
+ h["description"] = @description&.to_h unless @description.nil?
19
+ h["artifactLocation"] = @artifact_location&.to_h
20
+ h["regions"] = @regions&.map(&:to_h) if @regions&.any?
21
+ h["rectangles"] = @rectangles&.map(&:to_h) if @rectangles&.any?
22
+ h["properties"] = @properties unless @properties.nil?
23
+ h
24
+ end
25
+
26
+ def to_json(pretty: false)
27
+ pretty ? JSON.pretty_generate(to_h) : JSON.generate(to_h)
28
+ end
29
+
30
+ def self.from_hash(h)
31
+ return nil if h.nil?
32
+ new(
33
+ description: Message.from_hash(h["description"]),
34
+ artifact_location: ArtifactLocation.from_hash(h["artifactLocation"]),
35
+ regions: h["regions"]&.map { |v| Region.from_hash(v) } || [],
36
+ rectangles: h["rectangles"]&.map { |v| Rectangle.from_hash(v) } || [],
37
+ properties: h["properties"]
38
+ )
39
+ end
40
+
41
+ def ==(other)
42
+ return false unless other.is_a?(Attachment)
43
+ @description == other.description && @artifact_location == other.artifact_location && @regions == other.regions && @rectangles == other.rectangles && @properties == other.properties
44
+ end
45
+
46
+ alias eql? ==
47
+
48
+ def hash
49
+ [@description, @artifact_location, @regions, @rectangles, @properties].hash
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sarif
4
+ # A set of threadFlows which together describe a pattern of code execution relevant to detecting a result.
5
+ class CodeFlow
6
+ attr_accessor :message, :thread_flows, :properties
7
+
8
+ def initialize(message: nil, thread_flows:, properties: nil)
9
+ @message = message
10
+ @thread_flows = thread_flows
11
+ @properties = properties
12
+ end
13
+
14
+ def to_h
15
+ h = {}
16
+ h["message"] = @message&.to_h unless @message.nil?
17
+ h["threadFlows"] = @thread_flows&.map(&:to_h)
18
+ h["properties"] = @properties unless @properties.nil?
19
+ h
20
+ end
21
+
22
+ def to_json(pretty: false)
23
+ pretty ? JSON.pretty_generate(to_h) : JSON.generate(to_h)
24
+ end
25
+
26
+ def self.from_hash(h)
27
+ return nil if h.nil?
28
+ new(
29
+ message: Message.from_hash(h["message"]),
30
+ thread_flows: h["threadFlows"]&.map { |v| ThreadFlow.from_hash(v) },
31
+ properties: h["properties"]
32
+ )
33
+ end
34
+
35
+ def ==(other)
36
+ return false unless other.is_a?(CodeFlow)
37
+ @message == other.message && @thread_flows == other.thread_flows && @properties == other.properties
38
+ end
39
+
40
+ alias eql? ==
41
+
42
+ def hash
43
+ [@message, @thread_flows, @properties].hash
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sarif
4
+ # Information about how a specific rule or notification was reconfigured at runtime.
5
+ class ConfigurationOverride
6
+ attr_accessor :configuration, :descriptor, :properties
7
+
8
+ def initialize(configuration:, descriptor:, properties: nil)
9
+ @configuration = configuration
10
+ @descriptor = descriptor
11
+ @properties = properties
12
+ end
13
+
14
+ def to_h
15
+ h = {}
16
+ h["configuration"] = @configuration&.to_h
17
+ h["descriptor"] = @descriptor&.to_h
18
+ h["properties"] = @properties unless @properties.nil?
19
+ h
20
+ end
21
+
22
+ def to_json(pretty: false)
23
+ pretty ? JSON.pretty_generate(to_h) : JSON.generate(to_h)
24
+ end
25
+
26
+ def self.from_hash(h)
27
+ return nil if h.nil?
28
+ new(
29
+ configuration: ReportingConfiguration.from_hash(h["configuration"]),
30
+ descriptor: ReportingDescriptorReference.from_hash(h["descriptor"]),
31
+ properties: h["properties"]
32
+ )
33
+ end
34
+
35
+ def ==(other)
36
+ return false unless other.is_a?(ConfigurationOverride)
37
+ @configuration == other.configuration && @descriptor == other.descriptor && @properties == other.properties
38
+ end
39
+
40
+ alias eql? ==
41
+
42
+ def hash
43
+ [@configuration, @descriptor, @properties].hash
44
+ end
45
+ end
46
+ end