codeclimate 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,9 @@
1
+ module CC
2
+ module Analyzer
3
+ class NullConfig < Config
4
+ def initialize(*)
5
+ @config = { "engines" => {} }
6
+ end
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,14 @@
1
+ module CC
2
+ module Analyzer
3
+ class UnitName
4
+ attr_reader :full_name
5
+ attr_reader :local_name
6
+
7
+ def initialize(full_name, local_name)
8
+ @full_name = full_name
9
+ @local_name = local_name
10
+ end
11
+
12
+ end
13
+ end
14
+ 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