policial 0.0.3 → 0.0.4

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