Merge pull request #390 from crystal-ameba/refactor-rules-cli-switch

Refactor `--rules` CLI switch output + add `--describe <rule-name>` CLI switch
This commit is contained in:
Sijawusz Pur Rahnama 2023-11-05 06:44:55 +01:00 committed by GitHub
commit ddb6e3c38f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 191 additions and 16 deletions

View file

@ -0,0 +1,32 @@
require "../../spec_helper"
module Ameba
private def with_rule_collection_presenter(&)
with_presenter(Presenter::RuleCollectionPresenter) do |presenter, io|
rules = Config.load.rules
presenter.run(rules)
output = io.to_s
output = Formatter::Util.deansify(output).to_s
yield rules, output, presenter
end
end
describe Presenter::RuleCollectionPresenter do
it "outputs rule collection details" do
with_rule_collection_presenter do |rules, output|
rules.each do |rule|
output.should contain rule.name
output.should contain rule.severity.symbol
if description = rule.description
output.should contain description
end
end
output.should contain "Total rules: #{rules.size}"
output.should match /\d+ enabled/
end
end
end
end

View file

@ -0,0 +1,30 @@
require "../../spec_helper"
module Ameba
private def rule_presenter_each_rule(&)
with_presenter(Presenter::RulePresenter) do |presenter, io|
rules = Config.load.rules
rules.each do |rule|
presenter.run(rule)
output = io.to_s
output = Formatter::Util.deansify(output).to_s
yield rule, output, presenter
end
end
end
describe Presenter::RulePresenter do
it "outputs rule details" do
rule_presenter_each_rule do |rule, output|
output.should contain rule.name
output.should contain rule.severity.to_s
if description = rule.description
output.should contain description
end
end
end
end
end

View file

@ -282,6 +282,13 @@ module Ameba
end end
end end
def with_presenter(klass, &)
io = IO::Memory.new
presenter = klass.new(io)
yield presenter, io
end
def as_node(source) def as_node(source)
Crystal::Parser.new(source).parse Crystal::Parser.new(source).parse
end end

View file

@ -3,6 +3,7 @@ require "./ameba/ast/**"
require "./ameba/ext/**" require "./ameba/ext/**"
require "./ameba/rule/**" require "./ameba/rule/**"
require "./ameba/formatter/*" require "./ameba/formatter/*"
require "./ameba/presenter/*"
require "./ameba/source/**" require "./ameba/source/**"
# Ameba's entry module. # Ameba's entry module.

View file

@ -28,7 +28,14 @@ module Ameba::Cli
configure_rules(config, opts) configure_rules(config, opts)
if opts.rules? if opts.rules?
print_rules(config) print_rules(config.rules)
end
if describe_rule_name = opts.describe_rule
unless rule = config.rules.find(&.name.== describe_rule_name)
raise "Unknown rule"
end
describe_rule(rule)
end end
runner = Ameba.run(config) runner = Ameba.run(config)
@ -49,6 +56,7 @@ module Ameba::Cli
property globs : Array(String)? property globs : Array(String)?
property only : Array(String)? property only : Array(String)?
property except : Array(String)? property except : Array(String)?
property describe_rule : String?
property location_to_explain : NamedTuple(file: String, line: Int32, column: Int32)? property location_to_explain : NamedTuple(file: String, line: Int32, column: Int32)?
property fail_level : Severity? property fail_level : Severity?
property? skip_reading_config = false property? skip_reading_config = false
@ -119,6 +127,11 @@ module Ameba::Cli
configure_explain_opts(loc, opts) configure_explain_opts(loc, opts)
end end
parser.on("-d", "--describe Category/Rule",
"Describe a rule with specified name") do |rule_name|
configure_describe_opts(rule_name, opts)
end
parser.on("--without-affected-code", parser.on("--without-affected-code",
"Stop showing affected code while using a default formatter") do "Stop showing affected code while using a default formatter") do
opts.without_affected_code = true opts.without_affected_code = true
@ -152,6 +165,11 @@ module Ameba::Cli
opts.without_affected_code? opts.without_affected_code?
end end
private def configure_describe_opts(rule_name, opts)
opts.describe_rule = rule_name.presence
opts.formatter = :silent
end
private def configure_explain_opts(loc, opts) private def configure_explain_opts(loc, opts)
location_to_explain = parse_explain_location(loc) location_to_explain = parse_explain_location(loc)
opts.location_to_explain = location_to_explain opts.location_to_explain = location_to_explain
@ -183,14 +201,13 @@ module Ameba::Cli
exit 0 exit 0
end end
private def print_rules(config) private def describe_rule(rule)
config.rules.each do |rule| Presenter::RulePresenter.new.run(rule)
puts "%s [%s] - %s" % { exit 0
rule.name.colorize(:white), end
rule.severity.symbol.to_s.colorize(:green),
rule.description.colorize(:dark_gray), private def print_rules(rules)
} Presenter::RuleCollectionPresenter.new.run(rules)
end
exit 0 exit 0
end end
end end

View file

@ -4,8 +4,6 @@ module Ameba::Formatter
# A formatter that shows the detailed explanation of the issue at # A formatter that shows the detailed explanation of the issue at
# a specific location. # a specific location.
class ExplainFormatter class ExplainFormatter
HEADING_MARKER = "## "
include Util include Util
getter output : IO::FileDescriptor | IO::Memory getter output : IO::FileDescriptor | IO::Memory
@ -64,9 +62,8 @@ module Ameba::Formatter
rule.name.colorize(:magenta), rule.name.colorize(:magenta),
rule.severity.to_s.colorize(rule.severity.color), rule.severity.to_s.colorize(rule.severity.color),
} }
if rule_description = colorize_code_fences(rule.description)
if rule.responds_to?(:description) output_paragraph rule_description
output_paragraph rule.description
end end
rule_doc = colorize_code_fences(rule.class.parsed_doc) rule_doc = colorize_code_fences(rule.class.parsed_doc)
@ -84,7 +81,7 @@ module Ameba::Formatter
end end
private def output_title(title) private def output_title(title)
output << HEADING_MARKER.colorize(:yellow) output << "### ".colorize(:yellow)
output << title.upcase.colorize(:yellow) output << title.upcase.colorize(:yellow)
output << "\n\n" output << "\n\n"
end end
@ -95,7 +92,7 @@ module Ameba::Formatter
private def output_paragraph(paragraph : Array) private def output_paragraph(paragraph : Array)
paragraph.each do |line| paragraph.each do |line|
output << ' ' << line << '\n' output << " " << line << '\n'
end end
output << '\n' output << '\n'
end end

View file

@ -1,5 +1,7 @@
module Ameba::Formatter module Ameba::Formatter
module Util module Util
extend self
def deansify(message : String?) : String? def deansify(message : String?) : String?
message.try &.gsub(/\x1b[^m]*m/, "").presence message.try &.gsub(/\x1b[^m]*m/, "").presence
end end

View file

@ -0,0 +1,12 @@
module Ameba::Presenter
private ENABLED_MARK = "".colorize(:green)
private DISABLED_MARK = "x".colorize(:red)
class BasePresenter
# TODO: allow other IOs
getter output : IO::FileDescriptor | IO::Memory
def initialize(@output = STDOUT)
end
end
end

View file

@ -0,0 +1,34 @@
module Ameba::Presenter
class RuleCollectionPresenter < BasePresenter
def run(rules)
rules = rules.to_h do |rule|
name = rule.name.split('/')
name = "%s/%s" % {
name[0...-1].join('/').colorize(:light_gray),
name.last.colorize(:white),
}
{name, rule}
end
longest_name = rules.max_of(&.first.size)
rules.group_by(&.last.group).each do |group, group_rules|
output.puts "— %s" % group.colorize(:light_blue).underline
output.puts
group_rules.each do |name, rule|
output.puts " %s [%s] %s %s" % {
rule.enabled? ? ENABLED_MARK : DISABLED_MARK,
rule.severity.symbol.to_s.colorize(:green),
name.ljust(longest_name),
rule.description.colorize(:dark_gray),
}
end
output.puts
end
output.puts "Total rules: %s / %s enabled" % {
rules.size.to_s.colorize(:light_blue),
rules.count(&.last.enabled?).to_s.colorize(:light_blue),
}
end
end
end

View file

@ -0,0 +1,43 @@
module Ameba::Presenter
class RulePresenter < BasePresenter
def run(rule)
output.puts
output_title "Rule info"
output_paragraph "%s of a %s severity [enabled: %s]" % {
rule.name.colorize(:magenta),
rule.severity.to_s.colorize(rule.severity.color),
rule.enabled? ? ENABLED_MARK : DISABLED_MARK,
}
if rule_description = colorize_code_fences(rule.description)
output_paragraph rule_description
end
if rule_doc = colorize_code_fences(rule.class.parsed_doc)
output_title "Detailed description"
output_paragraph rule_doc
end
end
private def output_title(title)
output.print "### %s\n\n" % title.upcase.colorize(:yellow)
end
private def output_paragraph(paragraph : String)
output_paragraph(paragraph.lines)
end
private def output_paragraph(paragraph : Array)
paragraph.each do |line|
output.puts " #{line}"
end
output.puts
end
private def colorize_code_fences(string)
return unless string
string
.gsub(/```(.+?)```/m, &.colorize(:dark_gray))
.gsub(/`(?!`)(.+?)`/, &.colorize(:dark_gray))
end
end
end