Module: RailVerdict::Analyzers::Shared
- Defined in:
- lib/rail_verdict/analyzers/_shared.rb
Constant Summary collapse
- FALLBACK_MESSAGE_SUFFIX =
Canonical finding message normalization — single path for all analyzers. Guarantees deterministic, bounded, valid non-empty UTF-8.
"reported a finding without a message"
Class Method Summary collapse
- .bounded_message(message) ⇒ Object
- .canonical_tool_version(version) ⇒ Object
- .detail_for(run_result) ⇒ Object
- .execution_message(run_result) ⇒ Object
- .failure_result(analyzer_id:, invocation:, status:, message:, tool_version: nil) ⇒ Object
- .invocation_for(command, arguments) ⇒ Object
- .normalize_finding_message(analyzer_id, raw_message) ⇒ Object
- .normalize_tool_version(version) ⇒ Object
- .parse_semver(output) ⇒ Object
- .truncated?(run_result) ⇒ Boolean
Class Method Details
.bounded_message(message) ⇒ Object
8 9 10 |
# File 'lib/rail_verdict/analyzers/_shared.rb', line 8 def () .to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "?")[0, 4096] end |
.canonical_tool_version(version) ⇒ Object
71 72 73 74 |
# File 'lib/rail_verdict/analyzers/_shared.rb', line 71 def canonical_tool_version(version) normalized = normalize_tool_version(version) normalized.nil? || normalized.empty? ? "unknown" : normalized end |
.detail_for(run_result) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/rail_verdict/analyzers/_shared.rb', line 12 def detail_for(run_result) detail = run_result.detail.to_s stderr = run_result.stderr.to_s.lines.first.to_s.strip combined = [detail, stderr].reject(&:empty?).join(": ") if combined.empty? combined = if run_result.status == :exited "analyzer exited with status #{run_result.exit_code} and produced no diagnostic output" else "analyzer produced no diagnostic output" end end (combined) end |
.execution_message(run_result) ⇒ Object
26 27 28 29 30 31 |
# File 'lib/rail_verdict/analyzers/_shared.rb', line 26 def (run_result) = run_result.stderr.to_s.lines.first.to_s.strip = "#{run_result.detail}" if .empty? = "execution failed with status #{run_result.exit_code}" if .empty? () end |
.failure_result(analyzer_id:, invocation:, status:, message:, tool_version: nil) ⇒ Object
49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/rail_verdict/analyzers/_shared.rb', line 49 def failure_result(analyzer_id:, invocation:, status:, message:, tool_version: nil) status = status.to_s bounded = (.to_s) bounded = "#{status}: analyzer produced no diagnostic output" if bounded.strip.empty? normalized_version = normalize_tool_version(tool_version) AnalyzerResult.new( analyzer: analyzer_id, tool_version: normalized_version, invocation: invocation, execution_status: status, finding_ids: [], failure: { "code" => status, "message" => bounded } ) end |
.invocation_for(command, arguments) ⇒ Object
42 43 44 45 46 47 |
# File 'lib/rail_verdict/analyzers/_shared.rb', line 42 def invocation_for(command, arguments) { "executable" => command.fetch(:executable), "argv" => command.fetch(:args_prefix).dup.concat(arguments) } end |
.normalize_finding_message(analyzer_id, raw_message) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/rail_verdict/analyzers/_shared.rb', line 80 def (analyzer_id, ) raw = .to_s.dup # Handle invalid UTF-8, null bytes raw = raw.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "\uFFFD") raw = raw.scrub("\uFFFD") raw = raw.delete("\u0000") # Strip ANSI escape sequences raw = raw.gsub(/\e\[[0-9;]*[A-Za-z]/, "") raw = raw.gsub(/\e\][^\a]*\a/, "") raw = raw.gsub(/\e\(B/, "") # Remove control characters except tab/newline then normalize whitespace raw = raw.gsub(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/, "") raw = raw.strip # Collapse whitespace including tabs/newlines raw = raw.gsub(/\s+/, " ").strip if raw.empty? "#{analyzer_id} #{FALLBACK_MESSAGE_SUFFIX}" else raw = raw.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "\uFFFD").scrub("\uFFFD") raw.bytesize > 4096 ? raw.byteslice(0, 4096).scrub("\uFFFD").strip : raw end end |
.normalize_tool_version(version) ⇒ Object
64 65 66 67 68 69 |
# File 'lib/rail_verdict/analyzers/_shared.rb', line 64 def normalize_tool_version(version) return nil if version.nil? str = version.to_s.strip return nil if str.empty? str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "?").scrub("?")[0, 128] end |
.parse_semver(output) ⇒ Object
37 38 39 40 |
# File 'lib/rail_verdict/analyzers/_shared.rb', line 37 def parse_semver(output) match = output.to_s.match(/\b(\d+\.\d+\.\d+)\b/) match && match[1] end |
.truncated?(run_result) ⇒ Boolean
33 34 35 |
# File 'lib/rail_verdict/analyzers/_shared.rb', line 33 def truncated?(run_result) run_result.stdout_truncated || run_result.stderr_truncated end |