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 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