]> git.immae.eu Git - github/fretlink/pronto-hlint.git/blobdiff - lib/pronto/hlint/runner.rb
Welcom pronto-hlint! This is a whole rewrite of pronto-eslint_npm.
[github/fretlink/pronto-hlint.git] / lib / pronto / hlint / runner.rb
diff --git a/lib/pronto/hlint/runner.rb b/lib/pronto/hlint/runner.rb
new file mode 100644 (file)
index 0000000..e334860
--- /dev/null
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+require 'pronto'
+require 'shellwords'
+
+module Pronto
+  module Hlint
+    class Runner < Pronto::Runner
+      CONFIG_FILE = '.pronto_hlint.yml'.freeze
+      CONFIG_KEYS = %w[hlint_executable files_to_lint cmd_line_opts].freeze
+
+      attr_writer :hlint_executable, :cmd_line_opts
+
+      def hlint_executable
+        @hlint_executable || 'hlint'
+      end
+
+      def files_to_lint
+        @files_to_lint || /(\.hs)$/
+      end
+
+      def cmd_line_opts
+        @cmd_line_opts || ''
+      end
+
+      def files_to_lint=(regexp)
+        @files_to_lint = regexp.is_a?(Regexp) && regexp || Regexp.new(regexp)
+      end
+
+      def config_options
+        @config_options ||=
+          begin
+            config_file = File.join(repo_path, CONFIG_FILE)
+            File.exist?(config_file) && YAML.load_file(config_file) || {}
+          end
+      end
+
+      def read_config
+        config_options.each do |key, val|
+          next unless CONFIG_KEYS.include?(key.to_s)
+          send("#{key}=", val)
+        end
+      end
+
+      def run
+        return [] if !@patches || @patches.count.zero?
+
+        read_config
+
+        @patches
+          .select { |patch| patch.additions > 0 }
+          .select { |patch| hs_file?(patch.new_file_full_path) }
+          .map { |patch| inspect(patch) }
+          .flatten.compact
+      end
+
+      private
+
+      def repo_path
+        @repo_path ||= @patches.first.repo.path
+      end
+
+      def inspect(patch)
+        offences = run_hlint(patch)
+        offences
+          .map do |offence|
+          patch
+            .added_lines
+            .select { |line| (offence['startLine']..offence['endLine']).include?(line.new_lineno) }
+            .map { |line| new_message(offence, line) }
+        end
+      end
+
+      def new_message(offence, line)
+        path  = line.patch.delta.new_file[:path]
+        level = hlint_severity_to_pronto_level(offence['severity']) || :warning
+
+        text = <<~EOF
+        #{offence['severity']} offence detected by Hlint. Hint is: `#{offence['hint']}`.
+
+        Consider changing the code from
+        ```
+        #{offence['from']}
+        ```
+        to
+        ```
+        #{offence['to']}
+        ```
+      EOF
+
+        Message.new(path, line, level, text, nil, self.class)
+      end
+
+      def hlint_severity_to_pronto_level(severity)
+        case severity
+        when "Error"
+          :error
+        when "Warning"
+          :warning
+        when "Suggestion"
+          :info
+        end
+      end
+
+      def hs_file?(path)
+        files_to_lint =~ path.to_s
+      end
+
+      def run_hlint(patch)
+        Dir.chdir(repo_path) do
+          JSON.parse `#{hlint_command_line(patch.new_file_full_path.to_s)}`
+        end
+      end
+
+      def hlint_command_line(path)
+        "#{hlint_executable} #{cmd_line_opts} #{Shellwords.escape(path)} --json"
+      end
+    end
+  end
+end