lib/cc/analyzer/formatters/html_formatter.rb in codeclimate-0.44.0 vs lib/cc/analyzer/formatters/html_formatter.rb in codeclimate-0.45.0
- old
+ new
@@ -1,76 +1,410 @@
require "redcarpet"
-require "active_support/number_helper"
module CC
module Analyzer
module Formatters
- class HTMLFormatter < Formatter
+ class HTMLFormatter < Formatter # rubocop: disable Metrics/ClassLength
+ LANGUAGES = Hash.new { |_, ext| ext }.
+ merge(
+ # abap
+ # ada
+ "appraisals" => "ruby",
+ "as" => "actionscript",
+ "asm" => "nasm",
+ "bas" => "basic",
+ # c
+ "c++" => "cpp",
+ "capfile" => "ruby",
+ "cc" => "cpp",
+ "cfc" => "markup",
+ "cfm" => "markup",
+ "coffee" => "coffeescript",
+ "cp" => "cpp",
+ # cpp
+ "cr" => "crystal",
+ "cs" => "csharp",
+ "css" => %w[css css-extras],
+ "cu" => "cpp",
+ "cxx" => "cpp",
+ # d
+ # dart
+ # diff
+ "dockerfile" => "docker",
+ "dpr" => "pascal",
+ "erl" => "erlang",
+ "ex" => "elixir",
+ "f" => "fortran",
+ "f90" => "fortran",
+ "f95" => "fortran",
+ "feature" => "gherkin",
+ "for" => "fortran",
+ "fs" => "fsharp",
+ "fsi" => "fsharp",
+ "fsscript" => "fsharp",
+ "fsx" => "fsharp",
+ "gemfile" => "ruby",
+ "gemspec" => "ruby",
+ # glsl
+ # go
+ # groovy
+ "gvy" => "groovy",
+ "h" => "c",
+ "h++" => "cpp",
+ # haml
+ # handlebars
+ "hbr" => "handlebars",
+ "hh" => "cpp",
+ "hpp" => "cpp",
+ "hs" => "haskell",
+ "htm" => "markup",
+ "html" => "markup",
+ "hx" => "haxe",
+ "hxml" => "haxe",
+ "icn" => "icon",
+ "ijs" => "j",
+ # ini
+ "iol" => "jolie",
+ # java
+ "jl" => "julia",
+ "js" => "javascript",
+ # json
+ # jsx
+ "kt" => "kotlin",
+ "kts" => "kotlin",
+ # less
+ "lhs" => "haskell",
+ "lol" => "lolcode",
+ "lols" => "lolcode",
+ "ls" => "livescript",
+ # lua
+ "m" => "objective-c",
+ "mab" => "ruby",
+ # makefile
+ # markdown
+ "md" => "markdown",
+ # mel
+ "mkd" => "markdown",
+ "ml" => "ocaml",
+ "mli" => "ocaml",
+ "mm" => "objective-c",
+ # nim
+ # nix
+ "nsi" => "nsis",
+ "ol" => "jolie",
+ # oz
+ "pas" => "pascal",
+ "patch" => "diff",
+ "pde" => "processing",
+ "php" => %w[php php-extras],
+ "php3" => %w[php php-extras],
+ "php4" => %w[php php-extras],
+ "php5" => %w[php php-extras],
+ "phtml" => %w[php php-extras],
+ "pl" => "perl",
+ "pp" => "puppet",
+ "prawn" => "ruby",
+ "pro" => "prolog",
+ # properties
+ # pure
+ "py" => "python",
+ "py3" => "python",
+ "pyw" => "python",
+ "q" => "qore",
+ "qm" => "qore",
+ "qtest" => "qore",
+ # r
+ "rake" => "ruby",
+ "rakefile" => "ruby",
+ "rantfile" => "ruby",
+ "rb" => "ruby",
+ "rbw" => "ruby",
+ "rjs" => "ruby",
+ "rpdf" => "ruby",
+ "rs" => "rust",
+ "rst" => "rest",
+ "ru" => "ruby",
+ "rxml" => "ruby",
+ # sass
+ "sc" => "scala",
+ # scala
+ "scs" => "scheme",
+ # scss
+ "shader" => "glsl",
+ # sql
+ "ss" => "scheme",
+ "st" => "smalltalk",
+ "styl" => "stylus",
+ # swift
+ # tcl
+ "template" => "json",
+ "tex" => "latex",
+ # textile
+ "tmproj" => "markup",
+ "tpl" => "smarty",
+ "ts" => "typescript",
+ "v" => "verilog",
+ "vagrantfile" => "ruby",
+ "vhd" => "vhdl",
+ # vim
+ "xaml" => "markup",
+ "xhtml" => "markup",
+ "xml" => "markup",
+ # yaml
+ "yaws" => "erlang",
+ "yml" => "yaml",
+ ).freeze
+
+ class Location
+ CONTEXT_LINES = 2
+ MAX_LINES = 10
+
+ def initialize(source_buffer, location)
+ @source_buffer = source_buffer
+ @location = location
+ end
+
+ def begin_line
+ @begin_line ||= line("begin")
+ end
+
+ def end_line
+ @end_line ||= line("end")
+ end
+
+ def to_s
+ [
+ begin_line,
+ end_line,
+ ].uniq.join("-")
+ end
+
+ def start
+ [begin_line - CONTEXT_LINES, 1].max
+ end
+
+ def line_offset
+ start - 1
+ end
+
+ def code
+ first_line = start
+ last_line = [
+ end_line + CONTEXT_LINES,
+ begin_line + MAX_LINES + CONTEXT_LINES,
+ source_buffer.line_count,
+ ].min
+ source_buffer.source.lines[(first_line - 1)..(last_line - 1)].join("")
+ end
+
+ private
+
+ attr_reader :location, :source_buffer
+
+ def line(type)
+ if location["lines"]
+ location["lines"][type]
+ elsif location["positions"]
+ position_to_line(location["positions"][type])
+ end
+ end
+
+ def position_to_line(position)
+ if position["line"]
+ position["line"]
+ else
+ @source_buffer.decompose_position(position["offset"]).first
+ end
+ end
+ end
+
+ class SourceFile
+ def initialize(path, filesystem)
+ @path = path
+ @filesystem = filesystem
+ end
+
+ attr_reader :path
+
+ def syntaxes
+ ext = File.basename(path).split(".").last.downcase
+ Array(LANGUAGES[ext])
+ end
+
+ def code
+ filesystem.read_path(path)
+ end
+
+ def buffer
+ @buffer ||= SourceBuffer.new(path, code)
+ end
+
+ def location(loc)
+ Location.new(buffer, loc)
+ end
+
+ private
+
+ attr_reader :filesystem
+ end
+
+ class Issue
+ MARKDOWN_CONFIG = { autolink: true, fenced_code_blocks: true, no_intra_emphasis: true, tables: true }.freeze
+
+ def initialize(data, filesystem)
+ @data = data
+ @filesystem = filesystem
+ end
+
+ def description
+ data["description"]
+ end
+
+ def body
+ @body ||=
+ begin
+ text = data.fetch("content", {}).fetch("body", "").strip
+ unless text.empty?
+ markdown(text)
+ end
+ end
+ end
+
+ def source
+ @source ||= SourceFile.new(
+ data.fetch("location", {}).fetch("path", ""),
+ filesystem,
+ )
+ end
+
+ def location
+ @location ||=
+ Location.new(
+ source.buffer,
+ data["location"],
+ )
+ end
+
+ def other_locations
+ @other_locations ||=
+ begin
+ data.fetch("other_locations", []).map do |loc|
+ [SourceFile.new(loc["path"], filesystem), loc]
+ end.to_h
+ end
+ end
+
+ def categories
+ data.fetch("categories", [])
+ end
+
+ def engine_name
+ data["engine_name"]
+ end
+
+ private
+
+ attr_reader :data, :filesystem
+
+ def markdown(text)
+ html = Redcarpet::Render::HTML.new(
+ escape_html: false,
+ link_attributes: { target: "_blank" },
+ )
+ redcarpet = Redcarpet::Markdown.new(html, MARKDOWN_CONFIG)
+ redcarpet.render(text)
+ end
+ end
+
+ class IssueCollection
+ def initialize(filesystem)
+ @collection = []
+ @filesystem = filesystem
+ end
+
+ def each(&block)
+ collection.each(&block)
+ end
+
+ def <<(issue)
+ if issue.is_a? Hash
+ issue = Issue.new(issue, filesystem)
+ end
+ collection.push(issue)
+ end
+
+ def any?
+ collection.any?
+ end
+
+ def syntaxes
+ collection.flat_map do |issue|
+ issue.source.syntaxes
+ end.uniq.sort
+ end
+
+ def categories
+ collection.flat_map(&:categories).uniq.sort
+ end
+
+ def engines
+ collection.map(&:engine_name).uniq.compact.sort
+ end
+
+ private
+
+ attr_reader :collection, :filesystem
+ end
+
class ReportTemplate
include ERB::Util
- attr_accessor :template, :issues, :issues_by_path
+ attr_reader :issues
TEMPLATE_PATH = File.expand_path(File.join(File.dirname(__FILE__), "templates/html.erb"))
- def initialize(issue_count, issues_by_path)
- @template = File.read(TEMPLATE_PATH)
- @issues_by_path = issues_by_path
- @issue_count = issue_count
+ def initialize(issues, filesystem)
+ @issues = issues
+ @filesystem = filesystem
end
def render
- ERB.new(@template).result(binding)
+ template = File.read(TEMPLATE_PATH)
+ ERB.new(template, nil, "-").result(binding)
end
- def pluralize(number, noun)
- "#{ActiveSupport::NumberHelper.number_to_delimited(number)} #{noun.pluralize(number)}"
+ def project_name
+ File.basename(filesystem.root)
end
+
+ def param(str)
+ str.downcase.gsub(/\s+/, "-")
+ end
+
+ def params(values)
+ values.map { |c| param c }.join(" ")
+ end
+
+ private
+
+ attr_reader :filesystem
end
def finished
- template = ReportTemplate.new(issues.length, issues_by_path)
- puts template.render
+ puts ReportTemplate.new(issues, @filesystem).render
end
def failed(_)
exit 1
end
private
def issues
- @issues ||= []
+ @issues ||= IssueCollection.new(@filesystem)
end
- def issues_by_path
- issues.group_by { |i| i["location"]["path"] }.sort.each do |_, file_issues|
- IssueSorter.new(file_issues).by_location.map do |issue|
- source_buffer = @filesystem.source_buffer_for(issue["location"]["path"])
- issue["location"] = LocationDescription.new(source_buffer, issue["location"], "")
- issue["description"] = render_readup_markdown(issue["description"])
- if issue["content"]
- issue["content"]["body"] = render_readup_markdown(issue["content"]["body"])
- end
- end
- end
- end
-
def warnings
@warnings ||= []
- end
-
- def render_readup_markdown(body)
- html = Redcarpet::Render::HTML.new(escape_html: false, link_attributes: { target: "_blank" })
- Redcarpet::Markdown.new(html, markdown_options).render(body)
- end
-
- def markdown_options
- {
- autolink: true,
- fenced_code_blocks: true,
- no_intra_emphasis: true,
- tables: true,
- }
end
end
end
end
end