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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 464d6f323fb090523a3559db160cbd7b3595e5e0
4
- data.tar.gz: 7530dca475ec183d06b2c1cd034c6d9fd58f8c5c
3
+ metadata.gz: 6842c09f09430b3567358c4bc07a5f2915216b6b
4
+ data.tar.gz: 39475d633f642086e097cb601c76d7a4c2ff960a
5
5
  SHA512:
6
- metadata.gz: 45a2d8f3d1353a380f19edef20f2d0528f3a418a227a9e4ae538f400752459bf65e45f567243db33af687a4834e096f6744b8c265ec244a2ff6ecf5dacd3b42d
7
- data.tar.gz: c0e183378665f425dde7591de921cbdf2dca8019eabbd504ae260821e32829d20d6ce4c8fcc499fc083c1d3a8d0d5fd85d66e4770548877310d1a69249031cfb
6
+ metadata.gz: 67d8f45e400b833b025baa3b0e1aa18ebf19ec920f7ab92e83cd8d0c02ee37811ab40e9f2e5307e8b6e5b81f5f3e287d695c28c9782d48645da0ab967fb554f3
7
+ data.tar.gz: 3c3492bcf903f23da01a83e0f775799c4a984c479b45a7312cb6ba75746e9fa0a180c1cd7f7ae120c9aad5d44b06f5002acfd0b70f43dcdb862b643a10d1243d
data/bin/codeclimate ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "../lib")))
3
+
4
+ require "cc/cli"
5
+
6
+ ENV["FILESYSTEM_DIR"] ||= "."
7
+
8
+ CC::CLI::Runner.run(ARGV)
@@ -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
@@ -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