Module: ArchSpec::Formatters::Text
- Defined in:
- lib/archspec/formatters/text.rb
Overview
Prints diagnostics the way clang and herb do: a severity header with the rule id, the location, a code frame with the offending span underlined, and the evidence as a note.
[error] models must not depend on controllers [dependencies.forbid]
app/models/user.rb:3:3
2 │ class User
→ 3 │ UsersController
│ ^~~~~~~~~~~~~~~
4 │ end
note: User references UsersController
Output to a terminal is colored; a non-TTY or a NO_COLOR environment disables the colors.
Constant Summary collapse
- CONTEXT_LINES =
1
Class Method Summary collapse
-
.note_for(diagnostic, relative) ⇒ Object
The evidence as a note, or nil when it would only repeat the location shown above it, as parse-error evidence does.
- .print(output = $stdout, graph:, diagnostics:) ⇒ Object
- .print_diagnostic(output, style, graph, diagnostic, sources) ⇒ Object
- .print_frame(output, style, location, lines) ⇒ Object
- .read_lines(path) ⇒ Object
- .underline(location, text) ⇒ Object
Class Method Details
.note_for(diagnostic, relative) ⇒ Object
The evidence as a note, or nil when it would only repeat the location shown above it, as parse-error evidence does.
83 84 85 86 87 88 89 |
# File 'lib/archspec/formatters/text.rb', line 83 def note_for(diagnostic, relative) note = diagnostic.evidence.to_s return if note.empty? || note == relative note = "#{note} (confidence: #{diagnostic.confidence})" unless diagnostic.confidence == :high note end |
.print(output = $stdout, graph:, diagnostics:) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/archspec/formatters/text.rb', line 27 def print(output = $stdout, graph:, diagnostics:) if diagnostics.empty? output.puts "ArchSpec passed: #{graph.files.size} files, #{graph.constants.size} constants, " \ "#{graph.edges.size} facts checked." return end style = Style.new(output) sources = Hash.new { |hash, path| hash[path] = read_lines(path) } diagnostics.each do |diagnostic| print_diagnostic(output, style, graph, diagnostic, sources) end label = diagnostics.size == 1 ? 'architecture violation' : 'architecture violations' output.puts style.bold("#{diagnostics.size} #{label} found.") end |
.print_diagnostic(output, style, graph, diagnostic, sources) ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/archspec/formatters/text.rb', line 45 def print_diagnostic(output, style, graph, diagnostic, sources) location = diagnostic.location relative = location.relative_path(graph.root) output.puts "#{style.severity('[error]')} #{style.bold(diagnostic.)} #{style.faint("[#{diagnostic.rule}]")}" output.puts output.puts "#{relative}:#{location.line}:#{location.column}" print_frame(output, style, location, sources[location.path]) if (note = note_for(diagnostic, relative)) output.puts output.puts " #{style.note('note:')} #{note}" end output.puts end |
.print_frame(output, style, location, lines) ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/archspec/formatters/text.rb', line 61 def print_frame(output, style, location, lines) target = lines[location.line - 1] return unless target output.puts first = [location.line - CONTEXT_LINES, 1].max last = [location.line + CONTEXT_LINES, lines.size].min width = last.to_s.length (first..last).each do |number| text = lines[number - 1] if number == location.line output.puts " #{style.marker('→')} #{style.faint("#{number.to_s.rjust(width)} │")} #{text}" output.puts " #{style.faint("#{' ' * width} │")} #{style.marker(underline(location, target))}" else output.puts " #{style.faint("#{number.to_s.rjust(width)} │")} #{text}" end end end |
.read_lines(path) ⇒ Object
103 104 105 106 107 108 109 |
# File 'lib/archspec/formatters/text.rb', line 103 def read_lines(path) return [] unless File.file?(path) File.readlines(path, chomp: true).map { |line| line.scrub.tr("\t", ' ') } rescue SystemCallError [] end |
.underline(location, text) ⇒ Object
91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/archspec/formatters/text.rb', line 91 def underline(location, text) span = if location.end_line == location.line location.end_column - location.column else text.length - location.column + 1 end span = span.clamp(1, [text.length - location.column + 1, 1].max) "#{' ' * (location.column - 1)}^#{'~' * (span - 1)}" end |