Class: Ibex::Impact::Report
- Inherits:
-
Object
- Object
- Ibex::Impact::Report
- Defined in:
- lib/ibex/impact/report.rb,
sig/ibex/impact/report.rbs
Overview
Builds the deterministic public impact-v1 document.
Instance Method Summary collapse
- #action_documents(minimum) ⇒ Array[Hash[Symbol, Object?]]
- #action_production(action) ⇒ String
- #action_severity(name, current) ⇒ String
- #change_kinds(name) ⇒ Array[String]
- #component_names(node, name) ⇒ Array[String]
- #coverage_document ⇒ Hash[Symbol, Object?]
- #coverage_report_path(report) ⇒ String
- #document_severity(name, kinds) ⇒ String
- #empty_change ⇒ Hash[Symbol, Array[String]]
-
#initialize(mode:, algorithm:, grammar:, before:, after:, seeds:, nodes:, symbol_kinds:, set_changes:, automaton:, actions:, coverage:, metadata_names: [], minimum: "info", warnings: []) ⇒ Report
constructor
rubocop:disable Metrics/ParameterLists.
- #kind_severity(kind) ⇒ String
- #production_shape(production) ⇒ String?
- #reportable_symbol?(name) ⇒ Boolean
- #severity_of(record) ⇒ String
- #stable_grammar_digest(automaton) ⇒ String?
- #stable_location(location) ⇒ Hash[Symbol, Integer]?
- #stable_path(path) ⇒ String
- #stable_warnings ⇒ Array[String]
- #symbol_document(name) ⇒ Hash[Symbol, Object?]?
- #symbol_documents(minimum) ⇒ Array[Hash[Symbol, Object?]]
- #symbol_kinds_for(name, id) ⇒ Array[String]
- #symbol_name(id) ⇒ String
- #to_h(minimum: @minimum) ⇒ Hash[Symbol, Object?]
- #totals(symbols, actions, automaton) ⇒ Hash[Symbol, Integer]
- #witness_documents(witness) ⇒ Array[Hash[Symbol, Object?]]
Constructor Details
#initialize(mode:, algorithm:, grammar:, before:, after:, seeds:, nodes:, symbol_kinds:, set_changes:, automaton:, actions:, coverage:, metadata_names: [], minimum: "info", warnings: []) ⇒ Report
rubocop:disable Metrics/ParameterLists
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/ibex/impact/report.rb', line 33 def initialize(mode:, algorithm:, grammar:, before:, after:, seeds:, nodes:, symbol_kinds:, set_changes:, automaton:, actions:, coverage:, metadata_names: [], minimum: "info", warnings: []) @mode = mode @algorithm = algorithm @grammar = grammar @before = before @after = after @seeds = seeds @nodes = nodes @symbol_kinds = symbol_kinds @set_changes = set_changes @metadata_names = .uniq.sort @automaton = automaton #: Hash[Symbol, Object?] @actions = actions #: Array[Hash[Symbol, Object?]] @coverage = coverage #: Hash[Symbol, Object?] @minimum = minimum @warnings = warnings end |
Instance Method Details
#action_documents(minimum) ⇒ Array[Hash[Symbol, Object?]]
202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/ibex/impact/report.rb', line 202 def action_documents(minimum) documents = @actions.sort_by { |action| action_production(action) }.map do |action| production = action_production(action) severity = action.fetch(:severity) #: String reason = action.fetch(:reason) #: String context_length = action.fetch(:context_length) #: Hash[Symbol, Integer] location = action.fetch(:loc, nil) #: IR::location? { production: production, severity: severity, reason: reason, context_length: context_length, loc: stable_location(location) } end documents.select { |action| Severity::RANK.fetch(severity_of(action)) >= Severity::RANK.fetch(minimum) } end |
#action_production(action) ⇒ String
218 219 220 |
# File 'lib/ibex/impact/report.rb', line 218 def action_production(action) action.fetch(:production) #: String end |
#action_severity(name, current) ⇒ String
145 146 147 148 |
# File 'lib/ibex/impact/report.rb', line 145 def action_severity(name, current) findings = @actions.select { |action| action_production(action).start_with?("#{name} ->") } findings.reduce(current) { |level, finding| Severity.max(level, severity_of(finding)) } end |
#change_kinds(name) ⇒ Array[String]
109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/ibex/impact/report.rb', line 109 def change_kinds(name) change = @set_changes[name] return [] unless change kinds = [] #: Array[String] first = change.fetch(:first, empty_change) follow = change.fetch(:follow, empty_change) kinds << "first" unless first.fetch(:added).empty? && first.fetch(:removed).empty? kinds << "follow" unless follow.fetch(:added).empty? && follow.fetch(:removed).empty? kinds << "nullable" if change.fetch(:nullable, nil) kinds end |
#component_names(node, name) ⇒ Array[String]
129 130 131 132 133 |
# File 'lib/ibex/impact/report.rb', line 129 def component_names(node, name) return [name] unless node node.component.sort.map { |member| @grammar.symbol_by_id(member)&.name }.compact.sort end |
#coverage_document ⇒ Hash[Symbol, Object?]
223 224 225 226 227 228 229 230 231 232 |
# File 'lib/ibex/impact/report.rb', line 223 def coverage_document empty_reports = [] #: Array[Hash[Symbol, Object?]] reports = @coverage.fetch(:reports, empty_reports) #: Array[Hash[Symbol, Object?]] status = @coverage.fetch(:status) #: String { status: status, reports: reports.sort_by { |report| coverage_report_path(report) }.map do |report| productions = report.fetch(:productions) #: Array[Integer] { path: stable_path(coverage_report_path(report)), productions: productions.sort } end } end |
#coverage_report_path(report) ⇒ String
235 236 237 |
# File 'lib/ibex/impact/report.rb', line 235 def coverage_report_path(report) report.fetch(:path) #: String end |
#document_severity(name, kinds) ⇒ String
123 124 125 126 |
# File 'lib/ibex/impact/report.rb', line 123 def document_severity(name, kinds) level = kinds.reduce("info") { |current, kind| Severity.max(current, kind_severity(kind)) } action_severity(name, level) end |
#empty_change ⇒ Hash[Symbol, Array[String]]
156 157 158 |
# File 'lib/ibex/impact/report.rb', line 156 def empty_change { added: [], removed: [] } end |
#kind_severity(kind) ⇒ String
136 137 138 139 140 141 142 |
# File 'lib/ibex/impact/report.rb', line 136 def kind_severity(kind) return "high" if %w[first follow nullable action_arity].include?(kind) return "medium" if %w[reference precedence].include?(kind) return "low" if kind == "metadata" "info" end |
#production_shape(production) ⇒ String?
178 179 180 181 182 183 184 |
# File 'lib/ibex/impact/report.rb', line 178 def production_shape(production) return unless production lhs = symbol_name(production.lhs) rhs = production.rhs.map { |id| symbol_name(id) } "#{lhs} -> #{rhs.join(' ')}" end |
#reportable_symbol?(name) ⇒ Boolean
96 97 98 |
# File 'lib/ibex/impact/report.rb', line 96 def reportable_symbol?(name) !!(@grammar.symbol(name) || @set_changes.key?(name) || @metadata_names.include?(name)) end |
#severity_of(record) ⇒ String
151 152 153 |
# File 'lib/ibex/impact/report.rb', line 151 def severity_of(record) record.fetch(:severity) #: String end |
#stable_grammar_digest(automaton) ⇒ String?
256 257 258 259 260 261 262 263 |
# File 'lib/ibex/impact/report.rb', line 256 def stable_grammar_digest(automaton) return unless automaton serialized = IR::Serialize.dump(automaton.grammar) canonical = serialized.gsub(/"file":\s*"[^"]*"/, '"file": "<source>"') canonical = canonical.gsub(/"root":\s*"[^"]*"/, '"root": null') "sha256:#{Digest::SHA256.hexdigest(canonical)}" end |
#stable_location(location) ⇒ Hash[Symbol, Integer]?
249 250 251 252 253 |
# File 'lib/ibex/impact/report.rb', line 249 def stable_location(location) return unless location { line: location[:line], column: location[:column] } end |
#stable_path(path) ⇒ String
266 267 268 269 270 |
# File 'lib/ibex/impact/report.rb', line 266 def stable_path(path) return path unless path.start_with?(File::SEPARATOR) File.basename(path) end |
#stable_warnings ⇒ Array[String]
240 241 242 243 244 245 246 |
# File 'lib/ibex/impact/report.rb', line 240 def stable_warnings empty_reports = [] #: Array[Hash[Symbol, Object?]] paths = @coverage.fetch(:reports, empty_reports).map { |report| coverage_report_path(report) } @warnings.map do |warning| paths.reduce(warning) { |current, path| current.gsub(path, stable_path(path)) } end.sort.uniq end |
#symbol_document(name) ⇒ Hash[Symbol, Object?]?
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/ibex/impact/report.rb', line 79 def symbol_document(name) id = @grammar.symbol(name)&.id node = id && @nodes[id] return unless reportable_symbol?(name) kinds = symbol_kinds_for(name, id) kinds = ["reference"] if kinds.empty? && node severity = document_severity(name, kinds) { symbol: name, severity: severity, kinds: kinds, distance: node&.distance || 0, component: component_names(node, name), sets: @set_changes.fetch(name, { first: empty_change, follow: empty_change, nullable: nil }), witness: witness_documents(node&.witness || []) } end |
#symbol_documents(minimum) ⇒ Array[Hash[Symbol, Object?]]
71 72 73 74 75 76 |
# File 'lib/ibex/impact/report.rb', line 71 def symbol_documents(minimum) names = (@nodes.keys.filter_map { |id| @grammar.symbol_by_id(id)&.name } + @set_changes.keys + @metadata_names) .uniq.sort documents = names.filter_map { |name| symbol_document(name) } documents.select { |document| Severity::RANK.fetch(severity_of(document)) >= Severity::RANK.fetch(minimum) } end |
#symbol_kinds_for(name, id) ⇒ Array[String]
101 102 103 104 105 106 |
# File 'lib/ibex/impact/report.rb', line 101 def symbol_kinds_for(name, id) kinds = id ? (@symbol_kinds[id] || []).dup : [] #: Array[String] kinds << "metadata" if @metadata_names.include?(name) kinds.concat(change_kinds(name)) kinds.uniq.sort end |
#symbol_name(id) ⇒ String
173 174 175 |
# File 'lib/ibex/impact/report.rb', line 173 def symbol_name(id) @grammar.symbol_by_id(id)&.name || id.to_s end |
#to_h(minimum: @minimum) ⇒ Hash[Symbol, Object?]
55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/ibex/impact/report.rb', line 55 def to_h(minimum: @minimum) symbols = symbol_documents(minimum) action_records = action_documents(minimum) { ibex_report: "impact", schema_version: 1, mode: @mode, algorithm: @algorithm, grammar_digest: { before: stable_grammar_digest(@before), after: stable_grammar_digest(@after) }, seeds: @seeds.map { |seed| seed.slice(:symbol, :origin, :nullable_boundary) }, symbols: symbols, automaton: @automaton, actions: action_records, coverage: coverage_document, warnings: stable_warnings, totals: totals(symbols, action_records, @automaton) } end |
#totals(symbols, actions, automaton) ⇒ Hash[Symbol, Integer]
188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/ibex/impact/report.rb', line 188 def totals(symbols, actions, automaton) counts = Severity::LEVELS.to_h { |level| [level.to_sym, 0] } symbols.each { |item| counts[item.fetch(:severity).to_s.to_sym] += 1 } actions.each { |item| counts[item.fetch(:severity).to_s.to_sym] += 1 } conflicts = automaton.dig(:conflicts, :added) || [] #: Array[Hash[Symbol, Object?]] counts[:critical] += conflicts.length unreachable = automaton.fetch(:unreachable, []) #: Array[Integer] counts[:critical] += unreachable.length unreachable_nonterminals = automaton.fetch(:unreachable_nonterminals, []) #: Array[String] counts[:critical] += unreachable_nonterminals.length counts end |
#witness_documents(witness) ⇒ Array[Hash[Symbol, Object?]]
161 162 163 164 165 166 167 168 169 170 |
# File 'lib/ibex/impact/report.rb', line 161 def witness_documents(witness) witness.map do |edge| production = edge.production && @grammar.productions[edge.production] location = production&.origin&.fetch(:loc, nil) #: IR::location? { from: symbol_name(edge.source), to: symbol_name(edge.target), production: production_shape(production), loc: stable_location(location) } end end |