Class: RuboCop::Formatter::SARIFFormatter
| Relationships & Source Files | |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Class Chain:
self,
BaseFormatter
|
|
|
Instance Chain:
self,
BaseFormatter
|
|
| Inherits: |
RuboCop::Formatter::BaseFormatter
|
| Defined in: | lib/rubocop/formatter/sarif_formatter.rb |
Overview
This formatter formats the report data in SARIF 2.1.0, the OASIS standard for static analysis results. It’s understood by GitHub code scanning, Azure DevOps, and other tools that aggregate analyzer output.
SARIF only knows three severity levels, so RuboCop’s info, refactor
and convention severities are all reported as note.
Constant Summary
-
INFORMATION_URI =
# File 'lib/rubocop/formatter/sarif_formatter.rb', line 16'https://rubocop.org' -
SARIF_VERSION =
# File 'lib/rubocop/formatter/sarif_formatter.rb', line 15'2.1.0' -
SCHEMA_URI =
# File 'lib/rubocop/formatter/sarif_formatter.rb', line 14'https://json.schemastore.org/sarif-2.1.0.json' -
SEVERITY_LEVELS =
# File 'lib/rubocop/formatter/sarif_formatter.rb', line 18{ info: 'note', refactor: 'note', convention: 'note', warning: 'warning', error: 'error', fatal: 'error' }.freeze
Class Method Summary
Instance Attribute Summary
BaseFormatter - Inherited
Instance Method Summary
- #file_finished(file, offenses)
- #file_started(_file, options)
- #finished(_inspected_files)
-
#config_for(file)
private
#file_started is not called when a formatter is used directly through the API, so there may be no config to consult.
- #cop_config(file, offense) private
- #default_level_for(file, offense, cop_class) private
- #description_for(file, offense) private
- #driver private
-
#fingerprints_for(offense, line_hash_counts)
private
GitHub uses these to match an alert across runs.
- #help_for(description, help_uri) private
-
#location_for(file, offense)
private
The URI is relative to the working directory, which is what consumers expect to resolve against their own checkout.
- #region_for(offense) private
- #report private
- #result_for(file, offense, line_hash_counts) private
- #rule_for(file, offense) private
- #rule_index_for(file, offense) private
- #suppression_for(offense) private
- #uri_for(file) private
BaseFormatter - Inherited
| #file_finished | Invoked at the end of inspecting each files. |
| #file_problems | Invoked before #file_finished for a file that a cop crashed on or raised a warning for, or whose corrections went into an infinite loop. |
| #file_started | Invoked at the beginning of inspecting each files. |
| #finished | Invoked after all files are inspected or interrupted by user. |
| #started | Invoked once before any files are inspected. |
Constructor Details
.new(output, options = {}) ⇒ SARIFFormatter
# File 'lib/rubocop/formatter/sarif_formatter.rb', line 27
def initialize(output, = {}) super @rules = [] @rule_indexes = {} @results = [] @config_store = nil end
Instance Method Details
#config_for(file) (private)
#file_started is not called when a formatter is used directly through the API, so there may be no config to consult.
# File 'lib/rubocop/formatter/sarif_formatter.rb', line 177
def config_for(file) @config_store&.for_file(file) end
#cop_config(file, offense) (private)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 171
def cop_config(file, offense) config_for(file)&.for_cop(offense.cop_name) || {} end
#default_level_for(file, offense, cop_class) (private)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 164
def default_level_for(file, offense, cop_class) severity = cop_config(file, offense)['Severity'] || Cop::Base::DEPARTMENT_SEVERITIES.fetch(cop_class&.department, :convention) SEVERITY_LEVELS.fetch(severity.to_sym, 'warning') end
#description_for(file, offense) (private)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 160
def description_for(file, offense) cop_config(file, offense)['Description'] || offense.cop_name end
#driver (private)
[ GitHub ]#file_finished(file, offenses)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 40
def file_finished(file, offenses) line_hash_counts = Hash.new(0) offenses.each do |offense| @results << result_for(file, offense, line_hash_counts) end end
#file_started(_file, options)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 36
def file_started(_file, ) @config_store = [:config_store] end
#fingerprints_for(offense, line_hash_counts) (private)
GitHub uses these to match an alert across runs. Without them it falls
back to the file path, which makes alerts churn whenever a file moves.
Repeats of the same hash within a file get a :N suffix, like GitHub’s
own fingerprints, so two offenses on one line don’t merge into one alert.
The first occurrence keeps the bare hash to match earlier reports.
# File 'lib/rubocop/formatter/sarif_formatter.rb', line 117
def fingerprints_for(offense, line_hash_counts) line_hash = Digest::SHA256.hexdigest("#{offense.cop_name}#{offense.location.source_line}") count = line_hash_counts[line_hash] += 1 { primaryLocationLineHash: count == 1 ? line_hash : "#{line_hash}:#{count}" } end
#finished(_inspected_files)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 47
def finished(_inspected_files) output.write(report.to_json) end
#help_for(description, help_uri) (private)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 154
def help_for(description, help_uri) help = { text: description } help[:markdown] = "#{description}\n\n[Documentation](#{help_uri})" if help_uri help end
#location_for(file, offense) (private)
The URI is relative to the working directory, which is what consumers
expect to resolve against their own checkout. No uriBaseId: it would
have to be declared under originalUriBaseIds to mean anything, and
every consumer that matters resolves relative paths without it.
# File 'lib/rubocop/formatter/sarif_formatter.rb', line 92
def location_for(file, offense) { physicalLocation: { artifactLocation: { uri: uri_for(file) }, region: region_for(offense) } } end
#region_for(offense) (private)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 101
def region_for(offense) # SARIF columns are 1-based and `endColumn` points at the character # *after* the region, while `real_last_column` is the last character. { startLine: offense.first_line, startColumn: offense.real_column, endLine: offense.last_line, endColumn: offense.real_last_column + 1 } end
#report (private)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 53
def report { '$schema': SCHEMA_URI, version: SARIF_VERSION, runs: [{ tool: { driver: driver }, results: @results }] } end
#result_for(file, offense, line_hash_counts) (private)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 71
def result_for(file, offense, line_hash_counts) result = { ruleId: offense.cop_name, ruleIndex: rule_index_for(file, offense), level: SEVERITY_LEVELS.fetch(offense.severity.name, 'warning'), message: { text: offense. }, locations: [location_for(file, offense)], partialFingerprints: fingerprints_for(offense, line_hash_counts) } # Suppressed offenses are only reported under `--display-suppressed`. # SARIF has a first-class representation for them, so consumers can # tell them apart from offenses that were never triggered. result[:suppressions] = [suppression_for(offense)] if offense.disabled? result end
#rule_for(file, offense) (private)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 136
def rule_for(file, offense) cop_class = Cop::Registry.global.find_by_cop_name(offense.cop_name) description = description_for(file, offense) help_uri = cop_class && Cop::Documentation.url_for(cop_class, config_for(file)) rule = { id: offense.cop_name, name: offense.cop_name, shortDescription: { text: description }, fullDescription: { text: description }, help: help_for(description, help_uri), defaultConfiguration: { level: default_level_for(file, offense, cop_class) }, properties: { tags: [offense.cop_name.split('/').first] } } rule[:helpUri] = help_uri if help_uri rule end
#rule_index_for(file, offense) (private)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 129
def rule_index_for(file, offense) @rule_indexes[offense.cop_name] ||= begin @rules << rule_for(file, offense) @rules.size - 1 end end
#suppression_for(offense) (private)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 123
def suppression_for(offense) suppression = { kind: 'inSource' } suppression[:justification] = offense.justification if offense.justification suppression end
#uri_for(file) (private)
[ GitHub ]# File 'lib/rubocop/formatter/sarif_formatter.rb', line 181
def uri_for(file) PathUtil.smart_path(file).tr('\\', '/') end