codeclimate 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/codeclimate +8 -0
- data/config/engines.yml +95 -0
- data/lib/cc/analyzer.rb +26 -0
- data/lib/cc/analyzer/config.rb +73 -0
- data/lib/cc/analyzer/engine.rb +108 -0
- data/lib/cc/analyzer/engine_registry.rb +21 -0
- data/lib/cc/analyzer/filesystem.rb +48 -0
- data/lib/cc/analyzer/formatters.rb +19 -0
- data/lib/cc/analyzer/formatters/formatter.rb +29 -0
- data/lib/cc/analyzer/formatters/json_formatter.rb +28 -0
- data/lib/cc/analyzer/formatters/plain_text_formatter.rb +114 -0
- data/lib/cc/analyzer/formatters/spinner.rb +34 -0
- data/lib/cc/analyzer/issue_sorter.rb +30 -0
- data/lib/cc/analyzer/location_description.rb +51 -0
- data/lib/cc/analyzer/null_config.rb +9 -0
- data/lib/cc/analyzer/source_buffer.rb +49 -0
- data/lib/cc/analyzer/unit_name.rb +14 -0
- data/lib/cc/cli.rb +17 -0
- data/lib/cc/cli/analyze.rb +91 -0
- data/lib/cc/cli/command.rb +65 -0
- data/lib/cc/cli/console.rb +12 -0
- data/lib/cc/cli/engines.rb +14 -0
- data/lib/cc/cli/engines/disable.rb +31 -0
- data/lib/cc/cli/engines/enable.rb +35 -0
- data/lib/cc/cli/engines/engine_command.rb +47 -0
- data/lib/cc/cli/engines/install.rb +49 -0
- data/lib/cc/cli/engines/list.rb +14 -0
- data/lib/cc/cli/engines/remove.rb +29 -0
- data/lib/cc/cli/help.rb +36 -0
- data/lib/cc/cli/init.rb +53 -0
- data/lib/cc/cli/runner.rb +60 -0
- data/lib/cc/cli/validate_config.rb +75 -0
- data/lib/cc/cli/version.rb +15 -0
- metadata +217 -10
- data/lib/codeclimate.rb +0 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
module Formatters
|
4
|
+
class JSONFormatter < Formatter
|
5
|
+
|
6
|
+
def engine_running(engine)
|
7
|
+
@active_engine = engine
|
8
|
+
yield
|
9
|
+
@active_engine = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def write(data)
|
13
|
+
return unless data.present?
|
14
|
+
|
15
|
+
document = JSON.parse(data)
|
16
|
+
document["engine_name"] = @active_engine.name
|
17
|
+
puts document.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
def failed(output)
|
21
|
+
$stderr.puts "\nAnalysis failed with the following output:"
|
22
|
+
$stderr.puts output
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require "rainbow"
|
2
|
+
require "tty/spinner"
|
3
|
+
require "active_support/number_helper"
|
4
|
+
|
5
|
+
module CC
|
6
|
+
module Analyzer
|
7
|
+
module Formatters
|
8
|
+
class PlainTextFormatter < Formatter
|
9
|
+
|
10
|
+
def started
|
11
|
+
puts colorize("Starting analysis", :green)
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(data)
|
15
|
+
if data.present?
|
16
|
+
json = JSON.parse(data)
|
17
|
+
if @active_engine
|
18
|
+
json["engine_name"] = @active_engine.name
|
19
|
+
end
|
20
|
+
|
21
|
+
case json["type"].downcase
|
22
|
+
when "issue"
|
23
|
+
issues << json
|
24
|
+
when "warning"
|
25
|
+
warnings << json
|
26
|
+
else
|
27
|
+
raise "Invalid type found: #{json["type"]}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def finished
|
33
|
+
puts
|
34
|
+
|
35
|
+
issues_by_path.each do |path, file_issues|
|
36
|
+
puts colorize("== #{path} (#{pluralize(file_issues.size, "issue")}) ==", :yellow)
|
37
|
+
|
38
|
+
IssueSorter.new(file_issues).by_location.each do |issue|
|
39
|
+
if location = issue["location"]
|
40
|
+
print(colorize(LocationDescription.new(location, ": "), :cyan))
|
41
|
+
end
|
42
|
+
|
43
|
+
print(issue["description"])
|
44
|
+
print(colorize(" [#{issue["engine_name"]}]", "#333333"))
|
45
|
+
puts
|
46
|
+
end
|
47
|
+
puts
|
48
|
+
end
|
49
|
+
|
50
|
+
print(colorize("Analysis complete! Found #{pluralize(issues.size, "issue")}", :green))
|
51
|
+
if warnings.size > 0
|
52
|
+
print(colorize(" and #{pluralize(warnings.size, "warning")}", :green))
|
53
|
+
end
|
54
|
+
puts(colorize(".", :green))
|
55
|
+
end
|
56
|
+
|
57
|
+
def engine_running(engine)
|
58
|
+
@active_engine = engine
|
59
|
+
with_spinner("Running #{engine.name}: ") do
|
60
|
+
yield
|
61
|
+
end
|
62
|
+
@active_engine = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def failed(output)
|
66
|
+
spinner.stop("Failed")
|
67
|
+
puts colorize("\nAnalysis failed with the following output:", :red)
|
68
|
+
puts output
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def spinner(text = nil)
|
75
|
+
@spinner ||= Spinner.new(text)
|
76
|
+
end
|
77
|
+
|
78
|
+
def with_spinner(text)
|
79
|
+
spinner(text).start
|
80
|
+
yield
|
81
|
+
ensure
|
82
|
+
spinner.stop
|
83
|
+
@spinner = nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def colorize(string, *args)
|
87
|
+
rainbow.wrap(string).color(*args)
|
88
|
+
end
|
89
|
+
|
90
|
+
def rainbow
|
91
|
+
@rainbow ||= Rainbow.new.tap do |rainbow|
|
92
|
+
rainbow.enabled = false unless @output.tty?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def issues
|
97
|
+
@issues ||= []
|
98
|
+
end
|
99
|
+
|
100
|
+
def issues_by_path
|
101
|
+
issues.group_by { |i| i['location']['path'] }.sort
|
102
|
+
end
|
103
|
+
|
104
|
+
def warnings
|
105
|
+
@warnings ||= []
|
106
|
+
end
|
107
|
+
|
108
|
+
def pluralize(number, noun)
|
109
|
+
"#{ActiveSupport::NumberHelper.number_to_delimited(number)} #{noun.pluralize(number)}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
module Formatters
|
4
|
+
class Spinner
|
5
|
+
def initialize(text)
|
6
|
+
@spinner = TTY::Spinner.new(text)
|
7
|
+
end
|
8
|
+
|
9
|
+
def start
|
10
|
+
@thread = Thread.new do
|
11
|
+
loop do
|
12
|
+
@spinning = true
|
13
|
+
spinner.spin
|
14
|
+
sleep 0.075
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def stop(text = "Done!")
|
20
|
+
if @spinning
|
21
|
+
spinner.stop(text)
|
22
|
+
print("\n")
|
23
|
+
@thread.kill
|
24
|
+
end
|
25
|
+
@spinning = false
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :spinner
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
class IssueSorter
|
4
|
+
def initialize(issues)
|
5
|
+
@issues = issues
|
6
|
+
end
|
7
|
+
|
8
|
+
def by_location
|
9
|
+
@issues.sort_by { |i| line_or_offset(i) }
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def line_or_offset(issue)
|
15
|
+
location = issue["location"]
|
16
|
+
|
17
|
+
case
|
18
|
+
when location["lines"]
|
19
|
+
location["lines"]["begin"].to_i
|
20
|
+
when location["positions"] && location["positions"]["begin"]["line"]
|
21
|
+
location["positions"]["begin"]["line"].to_i + location["positions"]["begin"]["column"].to_i
|
22
|
+
when location["positions"] && location["positions"]["begin"]["offset"]
|
23
|
+
location["positions"]["begin"]["offset"].to_i + 1_000_000_000 # push offsets to end of list
|
24
|
+
else
|
25
|
+
0 # whole-file issues are first
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
class LocationDescription
|
4
|
+
def initialize(location, suffix = "")
|
5
|
+
@location = location
|
6
|
+
@suffix = suffix
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
str = ""
|
11
|
+
if location["lines"]
|
12
|
+
str << render_lines
|
13
|
+
elsif positions = location["positions"]
|
14
|
+
str << render_position(positions["begin"])
|
15
|
+
|
16
|
+
if positions["end"]
|
17
|
+
str << "-"
|
18
|
+
str << render_position(positions["end"])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
str << suffix unless str.blank?
|
23
|
+
|
24
|
+
str
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :location, :suffix
|
30
|
+
|
31
|
+
def render_lines
|
32
|
+
str = location["lines"]["begin"].to_s
|
33
|
+
str << "-#{location["lines"]["end"]}" if location["lines"]["end"]
|
34
|
+
str
|
35
|
+
end
|
36
|
+
|
37
|
+
def render_position(position)
|
38
|
+
str = ""
|
39
|
+
|
40
|
+
if position["line"]
|
41
|
+
str << position["line"].to_s
|
42
|
+
str << ":#{position["column"]}" if position["column"]
|
43
|
+
elsif position["offset"]
|
44
|
+
str << position["offset"].to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
str
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Adapted from https://github.com/whitequark/parser/blob/master/lib/parser/source/buffer.rb
|
2
|
+
module CC
|
3
|
+
module Analyzer
|
4
|
+
class SourceBuffer
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :source
|
7
|
+
|
8
|
+
def initialize(name, source)
|
9
|
+
@name = name
|
10
|
+
@source = source
|
11
|
+
end
|
12
|
+
|
13
|
+
def decompose_position(position)
|
14
|
+
line_no, line_begin = line_for(position)
|
15
|
+
|
16
|
+
[ 1 + line_no, position - line_begin ]
|
17
|
+
end
|
18
|
+
|
19
|
+
def line_count
|
20
|
+
@source.lines.count
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def line_for(position)
|
26
|
+
line_begins.bsearch do |line, line_begin|
|
27
|
+
line_begin <= position
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def line_begins
|
32
|
+
unless @line_begins
|
33
|
+
@line_begins, index = [ [ 0, 0 ] ], 1
|
34
|
+
|
35
|
+
@source.each_char do |char|
|
36
|
+
if char == "\n"
|
37
|
+
@line_begins.unshift [ @line_begins.length, index ]
|
38
|
+
end
|
39
|
+
|
40
|
+
index += 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@line_begins
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/cc/cli.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "active_support"
|
2
|
+
require "active_support/core_ext"
|
3
|
+
require "cc/analyzer"
|
4
|
+
|
5
|
+
module CC
|
6
|
+
module CLI
|
7
|
+
autoload :Analyze, "cc/cli/analyze"
|
8
|
+
autoload :Command, "cc/cli/command"
|
9
|
+
autoload :Console, "cc/cli/console"
|
10
|
+
autoload :Engines, "cc/cli/engines"
|
11
|
+
autoload :Help, "cc/cli/help"
|
12
|
+
autoload :Init, "cc/cli/init"
|
13
|
+
autoload :Runner, "cc/cli/runner"
|
14
|
+
autoload :ValidateConfig, "cc/cli/validate_config"
|
15
|
+
autoload :Version, "cc/cli/version"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
3
|
+
module CC
|
4
|
+
module CLI
|
5
|
+
class Analyze < Command
|
6
|
+
include CC::Analyzer
|
7
|
+
|
8
|
+
def initialize(args = [])
|
9
|
+
super
|
10
|
+
|
11
|
+
process_args
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
require_codeclimate_yml
|
16
|
+
if engines.empty?
|
17
|
+
fatal("No engines enabled. Add some to your .codeclimate.yml file!")
|
18
|
+
end
|
19
|
+
|
20
|
+
formatter.started
|
21
|
+
|
22
|
+
engines.each do |engine|
|
23
|
+
formatter.engine_running(engine) do
|
24
|
+
engine.run(formatter)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
formatter.finished
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def process_args
|
34
|
+
case @args.first
|
35
|
+
when '-f'
|
36
|
+
@args.shift # throw out the -f
|
37
|
+
@formatter = Formatters.resolve(@args.shift)
|
38
|
+
end
|
39
|
+
rescue Formatters::Formatter::InvalidFormatterError => e
|
40
|
+
fatal(e.message)
|
41
|
+
end
|
42
|
+
|
43
|
+
def config
|
44
|
+
@config ||= if filesystem.exist?(CODECLIMATE_YAML)
|
45
|
+
config_body = filesystem.read_path(CODECLIMATE_YAML)
|
46
|
+
config = Config.new(config_body)
|
47
|
+
else
|
48
|
+
config = NullConfig.new
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def engine_registry
|
53
|
+
@engine_registry ||= EngineRegistry.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def engine_config(engine_name)
|
57
|
+
config.engine_config(engine_name).
|
58
|
+
merge!(exclude_paths: exclude_paths).to_json
|
59
|
+
end
|
60
|
+
|
61
|
+
def exclude_paths
|
62
|
+
if config.exclude_paths
|
63
|
+
filesystem.files_matching(config.exclude_paths)
|
64
|
+
else
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def engines
|
70
|
+
@engines ||= config.engine_names.map do |engine_name|
|
71
|
+
Engine.new(
|
72
|
+
engine_name,
|
73
|
+
engine_registry[engine_name],
|
74
|
+
path,
|
75
|
+
engine_config(engine_name),
|
76
|
+
SecureRandom.uuid
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def formatter
|
82
|
+
@formatter ||= Formatters::PlainTextFormatter.new
|
83
|
+
end
|
84
|
+
|
85
|
+
def path
|
86
|
+
ENV['CODE_PATH']
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|