policial 0.0.3 → 0.0.4

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: 6b72a2344b1709d98d7dd121a15a8c8eab456961
4
- data.tar.gz: 0bccb0d25295dfabd7d0636eba66c4a6ff4f02d8
3
+ metadata.gz: 8cc78f631ce46e67c9b73b1b07aeb12ce9e1391a
4
+ data.tar.gz: 0e24d6c7a9cae9b3b0d1644dcdd937a7b751301c
5
5
  SHA512:
6
- metadata.gz: 2557808b3c2decb2b4a189901a4a5df6b3bede233d342134059ef02cf976621f6bbc636230a51cb37918ae800868bfb53ff9ce51495a909f5ef7a1e26d4868f1
7
- data.tar.gz: 39b7666177beeee05ecee1af8268c78b8c8d75f250672bb4d8ba9d5ccd6efe85f5174dde8595ff0aef5129459f7596a428f8809269cc10c0c3d3ba3f331a6441
6
+ metadata.gz: 7d40ae450bcc8fa6ca1fa029b3e642eeee559aed3012321044c2e77d17fc9c202f9a1838f7a365d56b57ef5b38a306d5e7dd52b60153d6f64a26bc8403e1f246
7
+ data.tar.gz: e5944b150dcd32d76dc3f82e86c799b3b6e359c76f8dbd36e3d2cb50c2b9baa9c1004653e8bd8df3f42526f8bf831f3d4756a4cd70d1eabb32e742a4f0cc5686
data/README.md CHANGED
@@ -7,9 +7,7 @@
7
7
  *Policial* is a gem that investigates pull requests and accuses style guide
8
8
  violations. It is based on thoughtbot's
9
9
  [Hound project](https://github.com/thoughtbot/hound).
10
- Currently it only investigates ruby code. You can setup your ruby code style
11
- rules by defining a `.rubocop.yml` file in you repo. Please see
12
- [RuboCop's README](https://github.com/bbatsov/rubocop).
10
+ It currently supports Ruby, SCSS and CoffeeScript.
13
11
 
14
12
  ## Installation
15
13
 
@@ -29,53 +27,98 @@ Or install it yourself as:
29
27
 
30
28
  ## Usage
31
29
 
32
- 1. You might need to instantiate an Octokit client with your GitHub
33
- credentials. For more information on this please check the
30
+ 1. First, instantiate a new Detective:
31
+ ```ruby
32
+ detective = Policial::Detective.new
33
+ ```
34
+
35
+ You might need to pass an Octokit client with your GitHub credentials.
36
+ For more information on this please check the
34
37
  [Octokit README](https://github.com/octokit/octokit.rb).
35
38
 
36
39
  ```ruby
37
- Policial.octokit = Octokit::Client.new(access_token: 'mygithubtoken666')
40
+ octokit = Octokit::Client.new(access_token: 'mygithubtoken666')
41
+ detective = Policial::Detective.new(octokit)
38
42
  ```
39
- Ignore this step if you want Policial to use the global Octokit configuration.
40
-
43
+ If you don't pass an Octokit client Policial will use the global Octokit
44
+ configuration.
41
45
 
42
- 2. Let's investigate! Start with a pull request which Policial will run an
43
- investigation against. You can setup a pull request manually:
46
+ 2. Let's investigate! Start by briefing your detective about the pull request it
47
+ will run an investigation against. You can setup a pull request manually:
44
48
 
45
49
  ```ruby
46
- pull_request = Policial::PullRequest.new(
50
+ detective.brief(
47
51
  repo: 'volmer/my_repo',
48
52
  number: 3,
49
53
  head_sha: 'headsha'
50
54
  )
51
55
  ```
52
56
 
53
- Or you can extract a pull request from a
57
+ Or you can brief it with a
54
58
  [GitHub `pull_request` webhook](https://developer.github.com/webhooks):
55
59
 
56
60
  ```ruby
57
61
  event = Policial::PullRequestEvent.new(webhook_payload)
58
- pull_request = event.pull_request
62
+ detective.brief(event)
59
63
  ```
60
64
 
61
65
  3. Now you can run the investigation:
62
66
 
63
67
  ```ruby
64
- investigation = Policial::Investigation.new(pull_request)
65
-
66
68
  # Let's investigate this pull request...
67
- investigation.run
69
+ detective.investigate
68
70
 
69
71
  # Want to know the violations found?
70
- investigation.violations
72
+ detective.violations
71
73
  ```
72
74
 
73
- 4. Hurry, post comments about those violations on the pull request!
75
+ 4. Want to know the violations found?
74
76
  ```ruby
75
- investigation.accuse
77
+ violations = detective.violations
78
+ # => [#<Policial::Violation:0x007ff0b5abad30 @filename="lib/test.rb", @line_number=1, ...>]
79
+
80
+ violations.first.message
81
+ "Prefer single-quoted strings when you don't need string interpolation or special symbols."
76
82
  ```
77
- The result are comments like this on each line that contains violations:
78
- ![image](https://cloud.githubusercontent.com/assets/301187/5545861/d5c3da76-8afe-11e4-8c15-341b01f3b820.png)
83
+
84
+ ## Ruby
85
+
86
+ You can setup your Ruby code style rules with a `.rubocop.yml` file in
87
+ your repo. Please see [RuboCop's README](https://github.com/bbatsov/rubocop).
88
+
89
+ ## CoffeeScript
90
+
91
+ You can setup your CoffeeScript code style rules with a `coffeelint.json`
92
+ file in your repo. For more information on how customize the linter rules please
93
+ visit the [Coffeelint website](https://coffelint.org).
94
+
95
+ ## SCSS
96
+
97
+ SCSS linting is disabled by default. To enable it, you need to install the
98
+ [SCSS-Lint](https://github.com/brigade/scss-lint) gem:
99
+
100
+ ```
101
+ gem install scss_lint
102
+ ```
103
+
104
+ Or add the following to your `Gemfile` and run `bundle install`:
105
+
106
+ ```ruby
107
+ gem 'scss_lint', require: false
108
+ ```
109
+
110
+ The `require: false` is necessary because `scss-lint` monkey patches `Sass`.
111
+ More info [here](https://github.com/brigade/scss-lint#installation).
112
+
113
+ Now you can enable SCSS on Policial:
114
+
115
+ ```ruby
116
+ Policial.style_guides << Policial::StyleGuides::Scss
117
+ ```
118
+
119
+ You can setup your SCSS code style rules with a `.scss-lint.yml` file in your
120
+ repo. For more information on how customize the linter rules please
121
+ read [SCSS-Lint's README](https://github.com/brigade/scss-lint#configuration).
79
122
 
80
123
  ## Contributing
81
124
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
3
5
  require 'rubocop/rake_task'
@@ -1,20 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'octokit'
2
4
 
3
- require 'policial/accusation_policy'
4
- require 'policial/commenter'
5
5
  require 'policial/commit'
6
6
  require 'policial/commit_file'
7
- require 'policial/octokit_client'
8
- require 'policial/investigation'
7
+ require 'policial/config_loader'
8
+ require 'policial/detective'
9
9
  require 'policial/line'
10
10
  require 'policial/patch'
11
11
  require 'policial/pull_request'
12
12
  require 'policial/pull_request_event'
13
- require 'policial/repo_config'
14
13
  require 'policial/style_checker'
15
14
  require 'policial/style_guides/base'
16
15
  require 'policial/style_guides/ruby'
17
- require 'policial/style_guides/unsupported'
16
+ require 'policial/style_guides/scss'
17
+ require 'policial/style_guides/coffeescript'
18
+ require 'policial/style_guides/javascript'
18
19
  require 'policial/unchanged_line'
19
20
  require 'policial/version'
20
21
  require 'policial/violation'
@@ -23,7 +24,23 @@ require 'policial/violation'
23
24
  # so you can configure GitHub credentials, enable/disable style guides
24
25
  # and more.
25
26
  module Policial
26
- extend OctokitClient
27
+ DEFAULT_STYLE_GUIDES = [
28
+ Policial::StyleGuides::Ruby,
29
+ Policial::StyleGuides::CoffeeScript,
30
+ Policial::StyleGuides::JavaScript
31
+ ].freeze
32
+
33
+ OPTIONAL_STYLE_GUIDES = [
34
+ Policial::StyleGuides::Scss
35
+ ].freeze
36
+
37
+ module_function
38
+
39
+ def style_guides
40
+ @style_guides ||= DEFAULT_STYLE_GUIDES.dup
41
+ end
27
42
 
28
- STYLE_GUIDES = [Policial::StyleGuides::Ruby]
43
+ def style_guides=(style_guides)
44
+ @style_guides = style_guides
45
+ end
29
46
  end
@@ -1,15 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Policial
2
4
  # Public: A Commit in a GitHub repo.
3
5
  class Commit
4
6
  attr_reader :repo, :sha
5
7
 
6
- def initialize(repo, sha)
8
+ def initialize(repo, sha, github_client)
7
9
  @repo = repo
8
10
  @sha = sha
11
+ @github_client = github_client
9
12
  end
10
13
 
11
14
  def file_content(filename)
12
- contents = Policial.octokit.contents(@repo, path: filename, ref: @sha)
15
+ contents = @github_client.contents(@repo, path: filename, ref: @sha)
13
16
 
14
17
  if contents && contents.content
15
18
  Base64.decode64(contents.content).force_encoding('UTF-8')
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Policial
2
4
  # Public: A file in a commit.
3
5
  class CommitFile
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Policial
4
+ # Public: Load and parse config files from GitHub repo.
5
+ class ConfigLoader
6
+ def initialize(commit)
7
+ @commit = commit
8
+ end
9
+
10
+ def raw(filename)
11
+ blank?(filename) ? '' : @commit.file_content(filename)
12
+ end
13
+
14
+ def json(filename)
15
+ JSON.parse(raw(filename))
16
+ rescue JSON::ParserError
17
+ {}
18
+ end
19
+
20
+ def yaml(filename)
21
+ YAML.load(raw(filename)) || {}
22
+ rescue Psych::SyntaxError
23
+ {}
24
+ end
25
+
26
+ private
27
+
28
+ def blank?(string)
29
+ string.to_s.strip.empty?
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Policial
4
+ # Public: Starting with an Octokit client and a pull request,
5
+ # it checks all changes introduced looking for style guide violations.
6
+ class Detective
7
+ attr_accessor :violations
8
+ attr_reader :github_client, :pull_request
9
+
10
+ def initialize(github_client = nil)
11
+ @github_client = github_client || Octokit
12
+ end
13
+
14
+ def brief(event_or_attributes)
15
+ pull_request_attributes = extract_attributes(event_or_attributes)
16
+ return unless pull_request_attributes
17
+ @pull_request = PullRequest.new(
18
+ pull_request_attributes.merge(github_client: @github_client)
19
+ )
20
+ end
21
+
22
+ def investigate(options = {})
23
+ return unless pull_request
24
+ @violations ||= StyleChecker.new(pull_request, options).violations
25
+ end
26
+
27
+ private
28
+
29
+ def extract_attributes(event_or_attributes)
30
+ if event_or_attributes.is_a?(PullRequestEvent)
31
+ event_or_attributes.pull_request_attributes
32
+ else
33
+ event_or_attributes
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Policial
2
4
  # Public: a changed line in a commit file.
3
5
  class Line
4
- attr_reader :number, :patch_position
6
+ attr_reader :content, :number, :patch_position
5
7
 
6
8
  def initialize(number, content, patch_position)
7
9
  @number = number
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Policial
2
4
  # Public: a chunk of changed code in a commit file.
3
5
  class Patch
@@ -1,21 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Policial
2
- # Public: A GibHub Pull Request.
4
+ # Public: A GitHub Pull Request.
3
5
  class PullRequest
4
6
  attr_reader :repo, :number, :user
7
+ attr_accessor :github_client
5
8
 
6
- def initialize(repo:, number:, head_sha:, user: nil)
7
- @repo = repo
8
- @number = number
9
+ def initialize(repo:, number:, head_sha:, github_client:, user: nil)
10
+ @repo = repo
11
+ @number = number
9
12
  @head_sha = head_sha
10
- @user = user
11
- end
12
-
13
- def comments
14
- @comments ||= fetch_comments
13
+ @user = user
14
+ @github_client = github_client
15
15
  end
16
16
 
17
17
  def files
18
- @files ||= Policial.octokit.pull_request_files(
18
+ @files ||= @github_client.pull_request_files(
19
19
  @repo, @number
20
20
  ).map do |file|
21
21
  build_commit_file(file)
@@ -23,7 +23,7 @@ module Policial
23
23
  end
24
24
 
25
25
  def head_commit
26
- @head_commit ||= Commit.new(@repo, @head_sha)
26
+ @head_commit ||= Commit.new(@repo, @head_sha, @github_client)
27
27
  end
28
28
 
29
29
  private
@@ -31,32 +31,5 @@ module Policial
31
31
  def build_commit_file(file)
32
32
  CommitFile.new(file, head_commit)
33
33
  end
34
-
35
- def fetch_comments
36
- paginate do |page|
37
- Policial.octokit.pull_request_comments(
38
- @repo,
39
- @number,
40
- page: page
41
- )
42
- end
43
- end
44
-
45
- private
46
-
47
- def paginate
48
- page, results, all_pages_fetched = 1, [], false
49
-
50
- until all_pages_fetched
51
- if (page_results = yield(page)).empty?
52
- all_pages_fetched = true
53
- else
54
- results += page_results
55
- page += 1
56
- end
57
- end
58
-
59
- results
60
- end
61
34
  end
62
35
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
 
3
5
  module Policial
@@ -9,19 +11,19 @@ module Policial
9
11
  @payload = payload
10
12
  end
11
13
 
12
- def pull_request
13
- @pull_request ||= PullRequest.new(
14
+ def pull_request_attributes
15
+ {
14
16
  repo: @payload['repository']['full_name'],
15
17
  number: @payload['number'],
16
18
  head_sha: @payload['pull_request']['head']['sha'],
17
19
  user: @payload['pull_request']['user']['login']
18
- )
20
+ }
19
21
  rescue NoMethodError
20
22
  nil
21
23
  end
22
24
 
23
25
  def should_investigate?
24
- !pull_request.nil? && (
26
+ !pull_request_attributes.nil? && (
25
27
  @payload['action'] == 'opened' || @payload['action'] == 'synchronize'
26
28
  )
27
29
  end
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Policial
2
4
  # Public: Filters files to reviewable subset, builds style guide based on file
3
5
  # extension and delegates to style guide for line violations.
4
6
  class StyleChecker
5
- def initialize(pull_request)
7
+ def initialize(pull_request, options = {})
6
8
  @pull_request = pull_request
7
9
  @style_guides = {}
10
+ @options = options
8
11
  end
9
12
 
10
13
  def violations
@@ -13,36 +16,31 @@ module Policial
13
16
 
14
17
  private
15
18
 
16
- attr_reader :pull_request, :style_guides
17
-
18
19
  def violations_in_checked_files
19
20
  files_to_check.flat_map do |file|
20
- style_guide(file.filename).violations_in_file(file)
21
+ style_guides.flat_map do |style_guide|
22
+ if style_guide.investigate?(file.filename)
23
+ style_guide.violations_in_file(file)
24
+ else
25
+ []
26
+ end
27
+ end
21
28
  end
22
29
  end
23
30
 
24
31
  def files_to_check
25
- pull_request.files.reject(&:removed?).select do |file|
26
- style_guide(file.filename).enabled?
27
- end
28
- end
29
-
30
- def style_guide(filename)
31
- style_guide_class = style_guide_class(filename)
32
- style_guides[style_guide_class] ||= style_guide_class.new(config)
32
+ @pull_request.files.reject(&:removed?)
33
33
  end
34
34
 
35
- def style_guide_class(filename)
36
- case filename
37
- when /.+\.rb\z/
38
- StyleGuides::Ruby
39
- else
40
- StyleGuides::Unsupported
35
+ def style_guides
36
+ Policial.style_guides.map do |klass|
37
+ @style_guides[klass] ||= klass.new(
38
+ config_loader, @options[klass::KEY] || {})
41
39
  end
42
40
  end
43
41
 
44
- def config
45
- @config ||= RepoConfig.new(pull_request.head_commit)
42
+ def config_loader
43
+ @config_loader ||= ConfigLoader.new(@pull_request.head_commit)
46
44
  end
47
45
  end
48
46
  end
@@ -1,17 +1,50 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Policial
2
4
  module StyleGuides
3
5
  # Public: Base to contain common style guide logic.
4
6
  class Base
5
- def initialize(repo_config)
6
- @repo_config = repo_config
7
+ def initialize(config_loader, options = {})
8
+ @config_loader = config_loader
9
+ @options = options
10
+ end
11
+
12
+ def violations_in_file(_file)
13
+ raise NotImplementedError, "must implement ##{__method__}"
14
+ end
15
+
16
+ def exclude_file?(_filename)
17
+ raise NotImplementedError, "must implement ##{__method__}"
18
+ end
19
+
20
+ def filename_pattern
21
+ raise NotImplementedError, "must implement ##{__method__}"
7
22
  end
8
23
 
24
+ def default_config_file
25
+ raise NotImplementedError, "must implement ##{__method__}"
26
+ end
27
+
28
+ def config_file
29
+ if @options[:config_file].to_s.strip.empty?
30
+ default_config_file
31
+ else
32
+ @options[:config_file]
33
+ end
34
+ end
35
+
36
+ def investigate?(filename)
37
+ enabled? && matches_pattern?(filename) && !exclude_file?(filename)
38
+ end
39
+
40
+ private
41
+
9
42
  def enabled?
10
- @repo_config.enabled_for?(self.class)
43
+ @options[:enabled] != false
11
44
  end
12
45
 
13
- def violations_in_file(_file)
14
- fail NotImplementedError, "must implement ##{__method__}"
46
+ def matches_pattern?(filename)
47
+ !(filename =~ filename_pattern).nil?
15
48
  end
16
49
  end
17
50
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'coffeelint'
4
+
5
+ module Policial
6
+ module StyleGuides
7
+ # Public: Determine CoffeeScript style guide violations per-line.
8
+ class CoffeeScript < Base
9
+ KEY = :coffeescript
10
+
11
+ def violations_in_file(file)
12
+ errors = Coffeelint.lint(file.content, config)
13
+ violations(file, errors)
14
+ end
15
+
16
+ def exclude_file?(_filename)
17
+ false
18
+ end
19
+
20
+ def filename_pattern
21
+ /.+\.coffee\z/
22
+ end
23
+
24
+ def default_config_file
25
+ 'coffeelint.json'
26
+ end
27
+
28
+ private
29
+
30
+ def config
31
+ @config ||= @config_loader.json(config_file)
32
+ end
33
+
34
+ def violations(file, errors)
35
+ errors.map do |error|
36
+ Violation.new(
37
+ file, error['lineNumber'], error['message'], error['rule'])
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'eslintrb'
4
+
5
+ module Policial
6
+ module StyleGuides
7
+ # Public: Determine Javascript style guide violations per-line.
8
+ class JavaScript < Base
9
+ KEY = :javascript
10
+
11
+ def violations_in_file(file)
12
+ errors = Eslintrb.lint(file.content, config)
13
+ violations(file, errors)
14
+ end
15
+
16
+ def exclude_file?(_filename)
17
+ false
18
+ end
19
+
20
+ def filename_pattern
21
+ /.+\.js\z/
22
+ end
23
+
24
+ def default_config_file
25
+ '.eslintrc.json'
26
+ end
27
+
28
+ private
29
+
30
+ def config
31
+ @config ||= begin
32
+ content = @config_loader.json(config_file)
33
+ content.empty? ? :defaults : content
34
+ end
35
+ end
36
+
37
+ def violations(file, errors)
38
+ errors.map do |error|
39
+ Violation.new(
40
+ file, error['line'], error['message'], error['ruleId'])
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,34 +1,54 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rubocop'
2
4
 
3
5
  module Policial
4
6
  module StyleGuides
5
7
  # Public: Determine Ruby style guide violations per-line.
6
8
  class Ruby < Base
7
- class << self
8
- attr_writer :config_file
9
+ KEY = :ruby
10
+
11
+ def violations_in_file(file)
12
+ offenses = team.inspect_file(parsed_source(file))
9
13
 
10
- def config_file
11
- @config_file || RuboCop::ConfigLoader::DOTFILE
14
+ offenses.reject(&:disabled?).map do |offense|
15
+ Violation.new(file, offense.line, offense.message, offense.cop_name)
12
16
  end
13
17
  end
14
18
 
15
- def violations_in_file(file)
16
- if config.file_to_exclude?(file.filename)
17
- []
18
- else
19
- offenses = team.inspect_file(parsed_source(file))
20
- build_violations(offenses, file)
21
- end
19
+ def exclude_file?(filename)
20
+ config.file_to_exclude?(filename)
21
+ end
22
+
23
+ def filename_pattern
24
+ /.+\.rb\z/
25
+ end
26
+
27
+ def default_config_file
28
+ RuboCop::ConfigLoader::DOTFILE
22
29
  end
23
30
 
24
31
  private
25
32
 
26
33
  def team
27
- RuboCop::Cop::Team.new(RuboCop::Cop::Cop.all, config, rubocop_options)
34
+ cop_classes = if config['Rails']['Enabled']
35
+ RuboCop::Cop::Cop.all
36
+ else
37
+ RuboCop::Cop::Cop.non_rails
38
+ end
39
+
40
+ RuboCop::Cop::Team.new(cop_classes, config)
28
41
  end
29
42
 
30
43
  def parsed_source(file)
31
- RuboCop::ProcessedSource.new(file.content, file.filename)
44
+ absolute_path =
45
+ File.join(config.base_dir_for_path_parameters, file.filename)
46
+
47
+ RuboCop::ProcessedSource.new(
48
+ file.content,
49
+ config['AllCops']['TargetRubyVersion'],
50
+ absolute_path
51
+ )
32
52
  end
33
53
 
34
54
  def config
@@ -36,31 +56,31 @@ module Policial
36
56
  end
37
57
 
38
58
  def custom_config
39
- custom = @repo_config.for(self.class)
59
+ content = @config_loader.yaml(config_file)
60
+ filter(content)
40
61
 
41
- if custom.is_a?(Hash)
42
- RuboCop::Config.new(custom, '').tap do |config|
43
- config.add_missing_namespaces
44
- config.make_excludes_absolute
45
- end
46
- else
47
- RuboCop::Config.new
62
+ tempfile_from(config_file, content.to_yaml) do |tempfile|
63
+ RuboCop::ConfigLoader.load_file(tempfile.path)
48
64
  end
49
65
  end
50
66
 
51
- def rubocop_options
52
- { debug: true } if config['ShowCopNames']
67
+ def tempfile_from(filename, content)
68
+ filename = File.basename(filename)
69
+ Tempfile.create(File.basename(filename), Dir.pwd) do |tempfile|
70
+ tempfile.write(content)
71
+ tempfile.rewind
72
+
73
+ yield(tempfile)
74
+ end
53
75
  end
54
76
 
55
- def build_violations(offenses, file)
56
- offenses.each_with_object({}) do |offense, violations|
57
- if violations[offense.line]
58
- violations[offense.line].add_messages([offense.message])
59
- else
60
- violations[offense.line] =
61
- Violation.new(file, offense.line, offense.message)
77
+ def filter(config_hash)
78
+ config_hash.delete('require')
79
+ config_hash.delete('inherit_gem')
80
+ config_hash['inherit_from'] =
81
+ Array(config_hash['inherit_from']).select do |value|
82
+ value =~ /\A#{URI.regexp(%w(http https))}\z/
62
83
  end
63
- end.values
64
84
  end
65
85
  end
66
86
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Policial
4
+ module StyleGuides
5
+ # Public: Determine SCSS style guide violations per-line.
6
+ class Scss < Base
7
+ KEY = :scss
8
+
9
+ def violations_in_file(file)
10
+ absolute_path = File.expand_path(file.filename)
11
+
12
+ runner = new_runner
13
+
14
+ tempfile_from(file.filename, file.content) do |tempfile|
15
+ runner.run([{ file: tempfile, path: absolute_path }])
16
+ end
17
+
18
+ violations(runner, file)
19
+ end
20
+
21
+ def exclude_file?(filename)
22
+ config.excluded_file?(File.expand_path(filename))
23
+ end
24
+
25
+ def filename_pattern
26
+ /.+\.scss\z/
27
+ end
28
+
29
+ def default_config_file
30
+ require 'scss_lint'
31
+ SCSSLint::Config::FILE_NAME
32
+ end
33
+
34
+ private
35
+
36
+ def config
37
+ require 'scss_lint'
38
+ @config ||= begin
39
+ content = @config_loader.raw(config_file)
40
+ tempfile_from(config_file, content) do |temp|
41
+ SCSSLint::Config.load(temp, merge_with_default: true)
42
+ end
43
+ end
44
+ end
45
+
46
+ def violations(runner, file)
47
+ runner.lints.map do |lint|
48
+ linter_name = lint.linter ? lint.linter.name : 'undefined'
49
+ Violation.new(
50
+ file, lint.location.line, lint.description, linter_name)
51
+ end
52
+ end
53
+
54
+ def new_runner
55
+ require 'scss_lint'
56
+ SCSSLint::Runner.new(config)
57
+ end
58
+
59
+ def tempfile_from(filename, content)
60
+ Tempfile.create(File.basename(filename), Dir.pwd) do |tempfile|
61
+ tempfile.write(content)
62
+ tempfile.rewind
63
+
64
+ yield(tempfile)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Policial
2
4
  # Public: An unchanged line in a commit file.
3
5
  class UnchangedLine
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Public: The gem version.
2
4
  module Policial
3
- VERSION = '0.0.3'
5
+ VERSION = '0.0.4'
4
6
  end
@@ -1,30 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Policial
2
- # Public: Hold file, line, and violation message values. Built by style
3
- # guides.
4
+ # Public: Hold file, line, and message. Built by style guides.
4
5
  class Violation
5
- attr_reader :line_number, :filename
6
+ attr_reader :line_number, :message, :linter
6
7
 
7
- def initialize(file, line_number, message)
8
- @filename = file.filename
9
- @line = file.line_at(line_number)
8
+ def initialize(file, line_number, message, linter)
9
+ @file = file
10
10
  @line_number = line_number
11
- @messages = [message]
11
+ @message = message
12
+ @linter = linter
12
13
  end
13
14
 
14
- def add_messages(messages)
15
- @messages += messages
15
+ def filename
16
+ @file.filename
16
17
  end
17
18
 
18
- def messages
19
- @messages.uniq
19
+ def line
20
+ @line ||= @file.line_at(line_number)
20
21
  end
21
22
 
22
23
  def patch_position
23
- @line.patch_position
24
+ line.patch_position
24
25
  end
25
26
 
26
27
  def on_changed_line?
27
- @line.changed?
28
+ line.changed?
28
29
  end
29
30
  end
30
31
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: policial
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Volmer Soares
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-05 00:00:00.000000000 Z
11
+ date: 2016-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: octokit
@@ -16,31 +16,59 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '3.7'
19
+ version: '4.3'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '3.7'
26
+ version: '4.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rubocop
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.2'
33
+ version: '0.39'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.2'
40
+ version: '0.39'
41
+ - !ruby/object:Gem::Dependency
42
+ name: coffeelint
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.14'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: eslintrb
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
41
69
  description: Review pull requests for style guide violations.
42
70
  email:
43
- - volmerius@gmail.com
71
+ - volmer@radicaos.com
44
72
  executables: []
45
73
  extensions: []
46
74
  extra_rdoc_files: []
@@ -49,21 +77,20 @@ files:
49
77
  - README.md
50
78
  - Rakefile
51
79
  - lib/policial.rb
52
- - lib/policial/accusation_policy.rb
53
- - lib/policial/commenter.rb
54
80
  - lib/policial/commit.rb
55
81
  - lib/policial/commit_file.rb
56
- - lib/policial/investigation.rb
82
+ - lib/policial/config_loader.rb
83
+ - lib/policial/detective.rb
57
84
  - lib/policial/line.rb
58
- - lib/policial/octokit_client.rb
59
85
  - lib/policial/patch.rb
60
86
  - lib/policial/pull_request.rb
61
87
  - lib/policial/pull_request_event.rb
62
- - lib/policial/repo_config.rb
63
88
  - lib/policial/style_checker.rb
64
89
  - lib/policial/style_guides/base.rb
90
+ - lib/policial/style_guides/coffeescript.rb
91
+ - lib/policial/style_guides/javascript.rb
65
92
  - lib/policial/style_guides/ruby.rb
66
- - lib/policial/style_guides/unsupported.rb
93
+ - lib/policial/style_guides/scss.rb
67
94
  - lib/policial/unchanged_line.rb
68
95
  - lib/policial/version.rb
69
96
  - lib/policial/violation.rb
@@ -87,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
114
  version: '0'
88
115
  requirements: []
89
116
  rubyforge_project:
90
- rubygems_version: 2.4.5
117
+ rubygems_version: 2.5.1
91
118
  signing_key:
92
119
  specification_version: 4
93
120
  summary: Review pull requests for style guide violations
@@ -1,34 +0,0 @@
1
- module Policial
2
- # Public: Checks if a given pull request should be investigated or not.
3
- class AccusationPolicy
4
- def initialize(pull_request)
5
- @pull_request = pull_request
6
- end
7
-
8
- def allowed_for?(violation)
9
- unreported_violation_messages(violation).any?
10
- end
11
-
12
- private
13
-
14
- def unreported_violation_messages(violation)
15
- violation.messages - existing_violation_messages(violation)
16
- end
17
-
18
- def existing_violation_messages(violation)
19
- previous_comments_on_line(violation).map(&:body)
20
- .flat_map { |body| body.split('<br>') }
21
- end
22
-
23
- def previous_comments_on_line(violation)
24
- existing_comments.select do |comment|
25
- comment.path == violation.filename &&
26
- comment.original_position == violation.patch_position
27
- end
28
- end
29
-
30
- def existing_comments
31
- @existing_comments ||= @pull_request.comments
32
- end
33
- end
34
- end
@@ -1,25 +0,0 @@
1
- module Policial
2
- # Public: Comment violations on pull request.
3
- class Commenter
4
- def initialize(pull_request)
5
- @pull_request = pull_request
6
- end
7
-
8
- def comment_violation(violation)
9
- Policial.octokit.create_pull_request_comment(
10
- @pull_request.repo,
11
- @pull_request.number,
12
- comment_body(violation),
13
- @pull_request.head_commit.sha,
14
- violation.filename,
15
- violation.patch_position
16
- )
17
- end
18
-
19
- private
20
-
21
- def comment_body(violation)
22
- violation.messages.join('<br/>')
23
- end
24
- end
25
- end
@@ -1,35 +0,0 @@
1
- module Policial
2
- # Public: Starting with unparsed data coming from a pull request event,
3
- # it checks all changes introduced looking for style guide violations. It
4
- # also accuse all present violations through comments on all relevant lines
5
- # in the pull request.
6
- class Investigation
7
- attr_accessor :violations, :pull_request
8
-
9
- def initialize(pull_request)
10
- @pull_request = pull_request
11
- end
12
-
13
- def run
14
- @violations ||= StyleChecker.new(@pull_request).violations
15
- end
16
-
17
- def accuse
18
- return if @violations.nil?
19
-
20
- commenter = Commenter.new(@pull_request)
21
-
22
- @violations.each do |violation|
23
- if accusation_policy.allowed_for?(violation)
24
- commenter.comment_violation(violation)
25
- end
26
- end
27
- end
28
-
29
- private
30
-
31
- def accusation_policy
32
- @accusation_policy ||= AccusationPolicy.new(@pull_request)
33
- end
34
- end
35
- end
@@ -1,10 +0,0 @@
1
- module Policial
2
- # Private: wraps accessors for the inner Octokit client.
3
- module OctokitClient
4
- attr_writer :octokit
5
-
6
- def octokit
7
- @octokit || Octokit
8
- end
9
- end
10
- end
@@ -1,36 +0,0 @@
1
- module Policial
2
- # Public: Load and parse config files from GitHub repo.
3
- class RepoConfig
4
- def initialize(commit)
5
- @commit = commit
6
- end
7
-
8
- def enabled_for?(style_guide_class)
9
- Policial::STYLE_GUIDES.include?(style_guide_class)
10
- end
11
-
12
- def for(style_guide_class)
13
- config_file = style_guide_class.config_file
14
-
15
- if config_file
16
- load_file(config_file)
17
- else
18
- {}
19
- end
20
- end
21
-
22
- private
23
-
24
- def load_file(file)
25
- config_file_content = @commit.file_content(file)
26
-
27
- parse_yaml(config_file_content) || {}
28
- end
29
-
30
- def parse_yaml(content)
31
- YAML.load(content)
32
- rescue Psych::SyntaxError
33
- {}
34
- end
35
- end
36
- end
@@ -1,10 +0,0 @@
1
- module Policial
2
- module StyleGuides
3
- # Public: Returns empty set of violations.
4
- class Unsupported < Base
5
- def violations_in_file(_)
6
- []
7
- end
8
- end
9
- end
10
- end