codeclimate 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6842c09f09430b3567358c4bc07a5f2915216b6b
|
4
|
+
data.tar.gz: 39475d633f642086e097cb601c76d7a4c2ff960a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67d8f45e400b833b025baa3b0e1aa18ebf19ec920f7ab92e83cd8d0c02ee37811ab40e9f2e5307e8b6e5b81f5f3e287d695c28c9782d48645da0ab967fb554f3
|
7
|
+
data.tar.gz: 3c3492bcf903f23da01a83e0f775799c4a984c479b45a7312cb6ba75746e9fa0a180c1cd7f7ae120c9aad5d44b06f5002acfd0b70f43dcdb862b643a10d1243d
|
data/bin/codeclimate
ADDED
data/config/engines.yml
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# This file lists all the engines available to be run for analysis.
|
2
|
+
#
|
3
|
+
# Each engine must have an `image_name` and `description`.
|
4
|
+
#
|
5
|
+
# When a repo has files that match the `enable_regexps`, that engine will be
|
6
|
+
# enabled by default in the codeclimate.yml file. That file will also have in it
|
7
|
+
# the `default_ratings_paths` globs, which are used during analysis to determine
|
8
|
+
# which files should be rated.
|
9
|
+
#
|
10
|
+
rubocop:
|
11
|
+
image_name: codeclimate/codeclimate-rubocop
|
12
|
+
image_id: 41273519bce24e59685106ff3f4519abc20233fdaf46093576cefecc093b795f
|
13
|
+
description: A Ruby static code analyzer, based on the community Ruby style guide.
|
14
|
+
community: false
|
15
|
+
enable_regexps:
|
16
|
+
- \.rb$
|
17
|
+
default_ratings_paths:
|
18
|
+
- "**.rb"
|
19
|
+
gofmt:
|
20
|
+
image_name: codeclimate/codeclimate-gofmt
|
21
|
+
image_id: fe7b20a9eda6e0bd154fbbb289f0d18ff3ffc41d891cd3b2e5dbe7e015180396
|
22
|
+
description: gofmt
|
23
|
+
community: true
|
24
|
+
enable_regexps:
|
25
|
+
- \.go$
|
26
|
+
default_ratings_paths:
|
27
|
+
- "**.go"
|
28
|
+
golint:
|
29
|
+
image_name: codeclimate/codeclimate-golint
|
30
|
+
image_id: cfde0099114d8b18bec8419d9ed359efac03069821cb967144b807d02ada1412
|
31
|
+
description: golint
|
32
|
+
community: true
|
33
|
+
enable_regexps:
|
34
|
+
- \.go$
|
35
|
+
default_ratings_paths:
|
36
|
+
- "**.go"
|
37
|
+
govet:
|
38
|
+
image_name: codeclimate/codeclimate-govet
|
39
|
+
image_id: fe7690da7198c569a28b69189909eb4c1199d2987bf50ab5d3bbd20d83139489
|
40
|
+
description: govet
|
41
|
+
community: true
|
42
|
+
enable_regexps:
|
43
|
+
- \.go$
|
44
|
+
default_ratings_paths:
|
45
|
+
- "**.go"
|
46
|
+
coffeelint:
|
47
|
+
image_name: codeclimate/codeclimate-coffeelint
|
48
|
+
description: A style checker for CoffeeScript
|
49
|
+
community: false
|
50
|
+
enable_regexps:
|
51
|
+
- \.coffee$
|
52
|
+
default_ratings_paths:
|
53
|
+
- "**.coffee"
|
54
|
+
eslint:
|
55
|
+
image_name: codeclimate/codeclimate-eslint
|
56
|
+
image_id: 28a9ec7c5fc88a67317b6193c3ddc72f1402ba2500e8ed3c6feab6d06ca91f65
|
57
|
+
description: A JavaScript/JSX linting utility
|
58
|
+
community: false
|
59
|
+
enable_regexps:
|
60
|
+
- \.js$
|
61
|
+
- \.jsx$
|
62
|
+
default_ratings_paths:
|
63
|
+
- "**.js"
|
64
|
+
- "**.jsx"
|
65
|
+
csslint:
|
66
|
+
image_name: codeclimate/codeclimate-csslint
|
67
|
+
image_id: 3d5aea2e5f241ff1daff33bd86cc5a819a34c81471e31ca70992f6de5e8edf43
|
68
|
+
description: Automated linting of Cascading Stylesheets
|
69
|
+
community: false
|
70
|
+
enable_regexps:
|
71
|
+
- \.css$
|
72
|
+
default_ratings_paths:
|
73
|
+
- "**.css"
|
74
|
+
watson:
|
75
|
+
image_name: codeclimate/codeclimate-watson
|
76
|
+
description: A young Ember Doctor to help you fix your code.
|
77
|
+
community: true
|
78
|
+
enable_regexps:
|
79
|
+
default_ratings_paths:
|
80
|
+
- "app/**"
|
81
|
+
rubymotion:
|
82
|
+
image_name: codeclimate/codeclimate-rubymotion
|
83
|
+
description: Rubymotion-specific rubocop checks
|
84
|
+
community: true
|
85
|
+
enable_regexps:
|
86
|
+
default_ratings_paths:
|
87
|
+
- "**.rb"
|
88
|
+
bundler-audit:
|
89
|
+
image_name: codeclimate/codeclimate-bundler-audit
|
90
|
+
description: Patch-level verification for Bundler
|
91
|
+
community: false
|
92
|
+
enable_patterns:
|
93
|
+
- Gemfile.lock
|
94
|
+
default_ratings_paths:
|
95
|
+
- Gemfile.lock
|
data/lib/cc/analyzer.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
autoload :Accumulator, "cc/analyzer/accumulator"
|
4
|
+
autoload :Config, "cc/analyzer/config"
|
5
|
+
autoload :Engine, "cc/analyzer/engine"
|
6
|
+
autoload :EngineClient, "cc/analyzer/engine_client"
|
7
|
+
autoload :EngineProcess, "cc/analyzer/engine_process"
|
8
|
+
autoload :EngineRegistry, "cc/analyzer/engine_registry"
|
9
|
+
autoload :Filesystem, "cc/analyzer/filesystem"
|
10
|
+
autoload :Formatters, "cc/analyzer/formatters"
|
11
|
+
autoload :IssueSorter, "cc/analyzer/issue_sorter"
|
12
|
+
autoload :LocationDescription,"cc/analyzer/location_description"
|
13
|
+
autoload :NullConfig, "cc/analyzer/null_config"
|
14
|
+
autoload :SourceBuffer, "cc/analyzer/source_buffer"
|
15
|
+
autoload :UnitName, "cc/analyzer/unit_name"
|
16
|
+
|
17
|
+
class DummyStatsd
|
18
|
+
def method_missing(*args)
|
19
|
+
yield if block_given?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
cattr_accessor :statsd
|
24
|
+
self.statsd = DummyStatsd.new
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module CC
|
4
|
+
module Analyzer
|
5
|
+
class Config
|
6
|
+
|
7
|
+
def initialize(config_body)
|
8
|
+
@config = YAML.safe_load(config_body) || {"engines"=> {} }
|
9
|
+
@config["engines"] ||= {}
|
10
|
+
|
11
|
+
expand_shorthand
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash
|
15
|
+
@config
|
16
|
+
end
|
17
|
+
|
18
|
+
def engine_config(engine_name)
|
19
|
+
@config["engines"][engine_name] || {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def engine_names
|
23
|
+
@config["engines"].keys.select { |name| engine_enabled?(name) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def engine_present?(engine_name)
|
27
|
+
@config["engines"][engine_name].present?
|
28
|
+
end
|
29
|
+
|
30
|
+
def engine_enabled?(engine_name)
|
31
|
+
@config["engines"][engine_name] && @config["engines"][engine_name]["enabled"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def enable_engine(engine_name)
|
35
|
+
if engine_present?(engine_name)
|
36
|
+
@config["engines"][engine_name]["enabled"] = true
|
37
|
+
else
|
38
|
+
@config["engines"][engine_name] = { "enabled" => true }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def exclude_paths
|
43
|
+
@config["exclude_paths"]
|
44
|
+
end
|
45
|
+
|
46
|
+
def disable_engine(engine_name)
|
47
|
+
if engine_present?(engine_name) && engine_enabled?(engine_name)
|
48
|
+
@config["engines"][engine_name]["enabled"] = false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def remove_engine(engine_name)
|
53
|
+
if engine_present?(engine_name)
|
54
|
+
@config["engines"].delete(engine_name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_yaml
|
59
|
+
@config.to_yaml
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def expand_shorthand
|
65
|
+
@config["engines"].each do |name, engine_config|
|
66
|
+
if [true, false].include?(engine_config)
|
67
|
+
@config["engines"][name] = { "enabled" => engine_config }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require "posix/spawn"
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
module CC
|
5
|
+
module Analyzer
|
6
|
+
class Engine
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
TIMEOUT = 15 * 60 # 15m
|
10
|
+
|
11
|
+
def initialize(name, metadata, code_path, config_json, label)
|
12
|
+
@name = name
|
13
|
+
@metadata = metadata
|
14
|
+
@code_path = code_path
|
15
|
+
@config_json = config_json
|
16
|
+
@label = label.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(stdout_io, stderr_io = StringIO.new)
|
20
|
+
pid, _, out, err = POSIX::Spawn.popen4(*docker_run_command)
|
21
|
+
engine_running = true
|
22
|
+
|
23
|
+
t_out = Thread.new do
|
24
|
+
out.each_line("\0") do |chunk|
|
25
|
+
stdout_io.write(chunk.chomp("\0"))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
t_err = Thread.new do
|
30
|
+
err.each_line do |line|
|
31
|
+
if stderr_io
|
32
|
+
stderr_io.write(line)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Thread.new do
|
38
|
+
sleep TIMEOUT
|
39
|
+
|
40
|
+
if engine_running
|
41
|
+
Thread.current.abort_on_exception = true
|
42
|
+
run_command("docker kill #{container_name}")
|
43
|
+
|
44
|
+
raise EngineTimeout, "engine #{name} ran past #{TIMEOUT} seconds and was killed"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
pid, status = Process.waitpid2(pid)
|
49
|
+
engine_running = false
|
50
|
+
Analyzer.statsd.increment("cli.engines.finished")
|
51
|
+
|
52
|
+
if status.success?
|
53
|
+
Analyzer.statsd.increment("cli.engines.names.#{name}.result.success")
|
54
|
+
Analyzer.statsd.increment("cli.engines.result.success")
|
55
|
+
else
|
56
|
+
Analyzer.statsd.increment("cli.engines.names.#{name}.result.error")
|
57
|
+
Analyzer.statsd.increment("cli.engines.result.error")
|
58
|
+
|
59
|
+
raise EngineFailure, "engine #{name} failed with status #{status.exitstatus} and stderr #{stderr_io.string.inspect}"
|
60
|
+
end
|
61
|
+
ensure
|
62
|
+
t_out.join if t_out
|
63
|
+
t_err.join if t_err
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def container_name
|
69
|
+
@container_name ||= "cc-engines-#{name}-#{SecureRandom.uuid}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def docker_run_command
|
73
|
+
[
|
74
|
+
"docker", "run",
|
75
|
+
"--rm",
|
76
|
+
"--cap-drop", "all",
|
77
|
+
"--label", "com.codeclimate.label=#{@label}",
|
78
|
+
"--name", container_name,
|
79
|
+
"--memory", 512_000_000.to_s, # bytes
|
80
|
+
"--memory-swap", "-1",
|
81
|
+
"--net", "none",
|
82
|
+
"--volume", "#{@code_path}:/code:ro",
|
83
|
+
"--env-file", env_file,
|
84
|
+
@metadata["image_name"],
|
85
|
+
@metadata["command"], # String or Array
|
86
|
+
].flatten.compact
|
87
|
+
end
|
88
|
+
|
89
|
+
def env_file
|
90
|
+
path = File.join("/tmp/cc", SecureRandom.uuid)
|
91
|
+
File.write(path, "ENGINE_CONFIG=#{@config_json}")
|
92
|
+
path
|
93
|
+
end
|
94
|
+
|
95
|
+
def run_command(command)
|
96
|
+
spawn = POSIX::Spawn::Child.new(command)
|
97
|
+
|
98
|
+
unless spawn.status.success?
|
99
|
+
raise CommandFailure, "command '#{command}' failed with status #{spawn.status.exitstatus} and output #{spawn.err}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
CommandFailure = Class.new(StandardError)
|
104
|
+
EngineFailure = Class.new(StandardError)
|
105
|
+
EngineTimeout = Class.new(StandardError)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "safe_yaml"
|
2
|
+
|
3
|
+
module CC
|
4
|
+
module Analyzer
|
5
|
+
class EngineRegistry
|
6
|
+
def initialize
|
7
|
+
@path = File.expand_path("../../../../config/engines.yml", __FILE__)
|
8
|
+
@config = YAML.safe_load_file(@path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](engine_name)
|
12
|
+
@config[engine_name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def list
|
16
|
+
@config
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
class Filesystem
|
4
|
+
|
5
|
+
def initialize(root)
|
6
|
+
@root = root
|
7
|
+
end
|
8
|
+
|
9
|
+
def exist?(path)
|
10
|
+
File.exist?(path_for(path))
|
11
|
+
end
|
12
|
+
|
13
|
+
def source_buffer_for(path)
|
14
|
+
SourceBuffer.new(path, read_path(path))
|
15
|
+
end
|
16
|
+
|
17
|
+
def read_path(path)
|
18
|
+
File.read(path_for(path))
|
19
|
+
end
|
20
|
+
|
21
|
+
def file_paths
|
22
|
+
Dir.chdir(@root) do
|
23
|
+
Dir["**/*.*"].select { |path| File.file?(path) }.sort
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def all
|
28
|
+
@files ||= Dir.chdir(@root) { Dir.glob("**/*") }
|
29
|
+
end
|
30
|
+
|
31
|
+
def any?(&block)
|
32
|
+
all.any?(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def files_matching(globs)
|
36
|
+
Dir.chdir(@root) do
|
37
|
+
globs.map do |glob|
|
38
|
+
Dir.glob(glob)
|
39
|
+
end.flatten.sort.uniq
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def path_for(path)
|
44
|
+
File.join(@root, path)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
module Formatters
|
4
|
+
autoload :Formatter, "cc/analyzer/formatters/formatter"
|
5
|
+
autoload :JSONFormatter, "cc/analyzer/formatters/json_formatter"
|
6
|
+
autoload :PlainTextFormatter, "cc/analyzer/formatters/plain_text_formatter"
|
7
|
+
autoload :Spinner, "cc/analyzer/formatters/spinner"
|
8
|
+
|
9
|
+
FORMATTERS = {
|
10
|
+
json: JSONFormatter,
|
11
|
+
text: PlainTextFormatter,
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
def self.resolve(name)
|
15
|
+
FORMATTERS[name.to_sym].new or raise InvalidFormatterError, "'#{name}' is not a valid formatter. Valid options are: #{FORMATTERS.keys.join(", ")}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
module Formatters
|
4
|
+
class Formatter
|
5
|
+
def initialize(output = $stdout)
|
6
|
+
@output = output
|
7
|
+
end
|
8
|
+
|
9
|
+
def write(data)
|
10
|
+
end
|
11
|
+
|
12
|
+
def started
|
13
|
+
end
|
14
|
+
|
15
|
+
def engine_running(engine)
|
16
|
+
yield
|
17
|
+
end
|
18
|
+
|
19
|
+
def finished
|
20
|
+
end
|
21
|
+
|
22
|
+
def failed(output)
|
23
|
+
end
|
24
|
+
|
25
|
+
InvalidFormatterError = Class.new(StandardError)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|