From a1854c0aa35a8febf4f2715c0001dc816069f04c Mon Sep 17 00:00:00 2001 From: Vitalii Elenhaupt Date: Thu, 30 Nov 2017 11:27:52 +0200 Subject: [PATCH] TODO formatter --- spec/ameba/formatter/todo_formatter_spec.cr | 44 +++++++++++++++ src/ameba/cli/cmd.cr | 22 ++++---- src/ameba/config.cr | 23 +++----- src/ameba/formatter/dot_formatter.cr | 2 +- src/ameba/formatter/todo_formatter.cr | 59 +++++++++++++++++++++ 5 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 spec/ameba/formatter/todo_formatter_spec.cr create mode 100644 src/ameba/formatter/todo_formatter.cr diff --git a/spec/ameba/formatter/todo_formatter_spec.cr b/spec/ameba/formatter/todo_formatter_spec.cr new file mode 100644 index 00000000..e0b946c8 --- /dev/null +++ b/spec/ameba/formatter/todo_formatter_spec.cr @@ -0,0 +1,44 @@ +require "../../spec_helper" + +module Ameba + private def create_todo(formatter) + s = Source.new "a = 1" + s.error DummyRule.new, s.location(1, 2), "message" + formatter.finished [s] + end + + describe Formatter::TODOFormatter do + file = IO::Memory.new + subject = Formatter::TODOFormatter.new IO::Memory.new, file + + context "problems not reported" do + it "does not create todo" do + subject.finished [Source.new ""] + file.to_s.empty?.should be_true + end + end + + context "problems reported" do + it "creates a todo with header" do + create_todo subject + file.to_s.should contain "# This configuration file was generated by" + file.to_s.should contain "Ameba version #{VERSION}" + end + + it "creates a todo with rule name" do + create_todo subject + file.to_s.should contain "DummyRule" + end + + it "creates a todo with problems count" do + create_todo subject + file.to_s.should contain "Problems found: 1" + end + + it "creates a valid YAML document" do + create_todo subject + YAML.parse(file.to_s).should_not be_nil + end + end + end +end diff --git a/src/ameba/cli/cmd.cr b/src/ameba/cli/cmd.cr index cec3393f..08c4212f 100644 --- a/src/ameba/cli/cmd.cr +++ b/src/ameba/cli/cmd.cr @@ -46,13 +46,11 @@ module Ameba::Cli def run_ameba(opts) config = Ameba::Config.load opts.config config.files = opts.files - config.formatter = Ameba::Formatter::BaseFormatter.new if opts.silent + configure_formatter(config, opts) configure_rules(config, opts) - runner = Ameba.run(config) - generate_config_file(config, runner, opts) if opts.generate - exit 1 unless runner.success? + exit 1 unless Ameba.run(config).success? rescue e puts "Error: #{e.message}" exit 255 @@ -71,14 +69,14 @@ module Ameba::Cli end end - private def generate_config_file(config, runner, opts) - failed_rules = - runner.sources - .map { |s| s.errors.map &.rule.name } - .flatten - .uniq! - failed_rules.each { |rule| config.update_rule rule, enabled: false } - File.write(opts.config, config.to_yaml) + private def configure_formatter(config, opts) + if opts.silent + config.formatter = Ameba::Formatter::BaseFormatter.new + elsif opts.generate + config.formatter = Ameba::Formatter::TODOFormatter.new + else + config.formatter = Ameba::Formatter::DotFormatter.new + end end private def print_version diff --git a/src/ameba/config.cr b/src/ameba/config.cr index 1167a13c..bb26c39f 100644 --- a/src/ameba/config.cr +++ b/src/ameba/config.cr @@ -20,7 +20,7 @@ class Ameba::Config # Creates a new instance of `Ameba::Config` based on YAML parameters. # # `Config.load` uses this constructor to instantiate new config by YAML file. - protected def initialize(@config : Hash(YAML::Type, YAML::Type)) + protected def initialize(@config : YAML::Any) @rules = Rule.rules.map &.new(config) end @@ -31,9 +31,9 @@ class Ameba::Config # ``` # def self.load(path = PATH) - content = File.exists?(path) ? File.read path : "{}" - Config.new YAML.parse(content).as_h - rescue e + content = File.exists?(path) ? File.read path : "" + Config.new YAML.parse(content) + rescue raise "Config file is invalid" end @@ -80,15 +80,6 @@ class Ameba::Config @rules[index] = rule end - def to_yaml(yaml : YAML::Builder) - yaml.mapping do - rules.each do |rule| - rule.name.to_yaml(yaml) - rule.to_yaml(yaml) - end - end - end - private def default_files Dir["**/*.cr"].reject(&.starts_with? "lib/") end @@ -158,8 +149,10 @@ class Ameba::Config properties {} def self.new(config = nil) - yaml = config.try &.[class_name]?.try &.to_yaml || "{}" - from_yaml yaml + if (raw = config.try &.raw).is_a? Hash + yaml = raw[class_name]?.try &.to_yaml + end + from_yaml yaml || "{}" end end end diff --git a/src/ameba/formatter/dot_formatter.cr b/src/ameba/formatter/dot_formatter.cr index a2323659..fcf4efb1 100644 --- a/src/ameba/formatter/dot_formatter.cr +++ b/src/ameba/formatter/dot_formatter.cr @@ -21,7 +21,7 @@ module Ameba::Formatter # Reports a message when inspection is finished. def finished(sources) output << "\n\n" - failed_sources = sources.reject { |s| s.valid? } + failed_sources = sources.reject &.valid? failed_sources.each do |source| source.errors.each do |error| diff --git a/src/ameba/formatter/todo_formatter.cr b/src/ameba/formatter/todo_formatter.cr new file mode 100644 index 00000000..c3a8b0c6 --- /dev/null +++ b/src/ameba/formatter/todo_formatter.cr @@ -0,0 +1,59 @@ +module Ameba::Formatter + # A formatter that creates a todo config. + # Basically, it takes all errors reported and disables corresponding rules + # or excludes failed sources from these rules. + class TODOFormatter < DotFormatter + @io : IO::FileDescriptor | IO::Memory + + def initialize(@output = STDOUT, @io = File.new(Config::PATH, mode: "w")) + end + + def finished(sources) + super + errors = sources.map(&.errors).flatten + generate_todo_config errors if errors.any? + if (io = @io).is_a?(File) + @output << "Created #{io.path}\n" + end + end + + private def generate_todo_config(errors) + @io << header + rule_errors_map(errors).each do |rule, rule_errors| + @io << "\n# Problems found: #{rule_errors.size}" + @io << rule_todo(rule, rule_errors).gsub("---", "") + end + ensure + @io.flush + end + + private def rule_errors_map(errors) + Hash(Rule::Base, Array(Source::Error)).new.tap do |h| + errors.each do |error| + h[error.rule] ||= Array(Source::Error).new + h[error.rule] << error + end + end + end + + private def header + <<-HEADER + # This configuration file was generated by `ameba --gen-config` + # on #{Time.now} using Ameba version #{VERSION}. + # The point is for the user to remove these configuration records + # one by one as the reported problems are removed from the code base. + + HEADER + end + + private def rule_todo(rule, errors) + rule.enabled = false + YAML.build do |yaml| + yaml.mapping do + rule.name.to_yaml(yaml) + rule.to_yaml(yaml) + end + end + end + end +end