rubocop-cask 0.1.0 → 0.2.0

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: 9cdfd026586c969df6476b261c953b19a97c0d96
4
- data.tar.gz: f5eec7b28ca3f9011a73505bb1caef81ccc09ed4
3
+ metadata.gz: eedae76807564012ce59a4b4a81af9751a851d2a
4
+ data.tar.gz: f7d783a06b07aaa8ddb2fdeec8f1ad9969851c40
5
5
  SHA512:
6
- metadata.gz: 087b2408052d624ca8c94b59de9314474261bdd63f728e0673763fc76a3a7e750ce4288d19580ff83d6342b722989e0bebb0dcd430692ca97efe90e117583ead
7
- data.tar.gz: 05aed98a4ea27b6ac1060476de4aa6b0a70a52ba9e41c7c2ff12824b9fbea4ff994e93bbb9bd40a6e8d6e6a582d6b28b7b4902bda59bb43a7660e1f6c6169fb8
6
+ metadata.gz: e3009d1c3e6233735a9d308720627f8e44a504270d730e52fb08b7734957bd4e4bd26d322a1baf3538e816b848dfcf533392ceb1fbc8c83c8b48ea4e0be9d90f
7
+ data.tar.gz: 65e425329851c85db2789774311a20b8fe6680638dc285ec703ee7e448be73e10bf6602de593ea639d855a912dd49355dd070c9fed9d2675e8e6e79fcf093e4c
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # RuboCop Cask
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/rubocop-cask.svg)](http://badge.fury.io/rb/rubocop-cask)
4
- [![Dependency Status](https://gemnasium.com/jawshooah/rubocop-cask.svg)](https://gemnasium.com/jawshooah/rubocop-cask)
5
- [![Build Status](https://travis-ci.org/jawshooah/rubocop-cask.svg?branch=master)](https://travis-ci.org/jawshooah/rubocop-cask)
6
- [![Coverage Status](https://img.shields.io/codeclimate/coverage/github/jawshooah/rubocop-cask.svg)](https://codeclimate.com/github/jawshooah/rubocop-cask)
7
- [![Code Climate](https://codeclimate.com/github/jawshooah/rubocop-cask/badges/gpa.svg)](https://codeclimate.com/github/jawshooah/rubocop-cask)
8
- [![Inline docs](http://inch-ci.org/github/jawshooah/rubocop-cask.svg)](http://inch-ci.org/github/jawshooah/rubocop-cask)
4
+ [![Dependency Status](https://gemnasium.com/caskroom/rubocop-cask.svg)](https://gemnasium.com/caskroom/rubocop-cask)
5
+ [![Build Status](https://travis-ci.org/caskroom/rubocop-cask.svg?branch=master)](https://travis-ci.org/caskroom/rubocop-cask)
6
+ [![Coverage Status](https://img.shields.io/codeclimate/coverage/github/caskroom/rubocop-cask.svg)](https://codeclimate.com/github/caskroom/rubocop-cask)
7
+ [![Code Climate](https://codeclimate.com/github/caskroom/rubocop-cask/badges/gpa.svg)](https://codeclimate.com/github/caskroom/rubocop-cask)
8
+ [![Inline docs](http://inch-ci.org/github/caskroom/rubocop-cask.svg)](http://inch-ci.org/github/caskroom/rubocop-cask)
9
9
 
10
10
  Cask-specific analysis for your Homebrew-Cask taps, as an extension to
11
11
  [RuboCop](https://github.com/bbatsov/rubocop). Heavily inspired by [`rubocop-rspec`](https://github.com/nevir/rubocop-rspec).
@@ -78,7 +78,7 @@ Cask/NoDslVersion:
78
78
  For running the spec files, this project depends on RuboCop's spec helpers. This means that in order to run the specs locally, you need a (shallow) clone of the RuboCop repository:
79
79
 
80
80
  ```bash
81
- git clone --depth 1 git://github.com/bbatsov/rubocop.git vendor/rubocop
81
+ git submodule update --init --depth 1 vendor/rubocop
82
82
  ```
83
83
 
84
84
  ## License
data/config/default.yml CHANGED
@@ -1,3 +1,11 @@
1
1
  Cask/NoDslVersion:
2
2
  Description: 'Do not use the deprecated DSL version syntax in your cask header.'
3
3
  Enabled: true
4
+
5
+ Cask/StanzaOrder:
6
+ Description: 'Ensure that cask stanzas are sorted correctly. More info at https://github.com/caskroom/homebrew-cask/blob/master/CONTRIBUTING.md#stanza-order'
7
+ Enabled: true
8
+
9
+ Cask/StanzaGrouping:
10
+ Description: 'Ensure that cask stanzas are grouped correctly. More info at https://github.com/caskroom/homebrew-cask/blob/master/CONTRIBUTING.md#stanza-order'
11
+ Enabled: true
data/lib/rubocop-cask.rb CHANGED
@@ -1,12 +1,17 @@
1
1
  require 'rubocop'
2
2
 
3
- require 'rubocop/cask/node_help'
3
+ require 'rubocop/cask/constants'
4
+ require 'rubocop/cask/extend/string'
5
+ require 'rubocop/cask/extend/astrolabe/node'
4
6
  require 'rubocop/cask/cask_help'
5
- require 'rubocop/cask/cask_header'
6
- require 'rubocop/cask/cask_block'
7
+ require 'rubocop/cask/ast/cask_header'
8
+ require 'rubocop/cask/ast/cask_block'
9
+ require 'rubocop/cask/ast/stanza'
7
10
  require 'rubocop/cask/version'
8
11
  require 'rubocop/cask/inject'
9
12
 
10
13
  RuboCop::Cask::Inject.defaults!
11
14
 
12
15
  require 'rubocop/cop/cask/no_dsl_version'
16
+ require 'rubocop/cop/cask/stanza_order'
17
+ require 'rubocop/cop/cask/stanza_grouping'
@@ -0,0 +1,61 @@
1
+ require 'forwardable'
2
+
3
+ module RuboCop
4
+ module Cask
5
+ module AST
6
+ # This class wraps the AST block node that represents the entire cask
7
+ # definition. It includes various helper methods to aid cops in their
8
+ # analysis.
9
+ class CaskBlock
10
+ extend Forwardable
11
+
12
+ def initialize(block_node)
13
+ @block_node = block_node
14
+ end
15
+
16
+ attr_reader :block_node
17
+
18
+ alias_method :cask_node, :block_node
19
+
20
+ def cask_body
21
+ cask_node.block_body
22
+ end
23
+
24
+ def header
25
+ @header ||= CaskHeader.new(cask_node.method_node)
26
+ end
27
+
28
+ def stanzas
29
+ @stanzas ||= cask_body.each_descendant(:send)
30
+ .map { |n| Stanza.new(n) }
31
+ end
32
+
33
+ def toplevel_stanzas
34
+ @toplevel_stanzas ||= stanzas.select(&:toplevel_stanza?)
35
+ end
36
+
37
+ def sorted_toplevel_stanzas
38
+ @sorted_toplevel_stanzas ||= sort_stanzas(toplevel_stanzas)
39
+ end
40
+
41
+ private
42
+
43
+ def sort_stanzas(stanzas)
44
+ stanzas.sort do |s1, s2|
45
+ i1, i2 = [s1, s2].map { |s| stanza_order_index(s) }
46
+ if i1 != i2
47
+ i1 - i2
48
+ else
49
+ i1, i2 = [s1, s2].map { |s| stanzas.index(s) }
50
+ i1 - i2
51
+ end
52
+ end
53
+ end
54
+
55
+ def stanza_order_index(stanza)
56
+ Constants::STANZA_ORDER.index(stanza.stanza_name)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,61 @@
1
+ module RuboCop
2
+ module Cask
3
+ module AST
4
+ # This class wraps the AST method node that represents the cask header. It
5
+ # includes various helper methods to aid cops in their analysis.
6
+ class CaskHeader
7
+ include CaskHelp
8
+
9
+ attr_reader :method_node
10
+
11
+ def initialize(method_node)
12
+ @method_node = method_node
13
+ end
14
+
15
+ def dsl_version?
16
+ hash_node
17
+ end
18
+
19
+ def test_cask?
20
+ header_method_name == :test_cask || dsl_version.to_s.include?('test')
21
+ end
22
+
23
+ def header_str
24
+ @header_str ||= source_range.source
25
+ end
26
+
27
+ def source_range
28
+ @source_range ||= method_node.loc.expression
29
+ end
30
+
31
+ def preferred_header_str
32
+ "#{preferred_header_method_name} '#{cask_token}'"
33
+ end
34
+
35
+ def preferred_header_method_name
36
+ test_cask? ? :test_cask : :cask
37
+ end
38
+
39
+ def header_method_name
40
+ @header_method_name ||= method_node.method_name
41
+ end
42
+
43
+ def dsl_version
44
+ @dsl_version ||= pair_node.key_node.children.first
45
+ end
46
+
47
+ def cask_token
48
+ @cask_token ||= pair_node.val_node.children.first
49
+ end
50
+
51
+ def hash_node
52
+ @hash_node ||= method_node.each_child_node(:hash).first
53
+ end
54
+
55
+ def pair_node
56
+ @pair_node ||= hash_node.each_child_node(:pair).first
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,57 @@
1
+ require 'forwardable'
2
+
3
+ module RuboCop
4
+ module Cask
5
+ module AST
6
+ # This class wraps the AST send node that encapsulates the method call
7
+ # that comprises the stanza. It includes various helper methods to aid
8
+ # cops in their analysis.
9
+ class Stanza
10
+ extend Forwardable
11
+
12
+ def initialize(method_node)
13
+ @method_node = method_node
14
+ end
15
+
16
+ attr_reader :method_node
17
+
18
+ alias_method :stanza_node, :method_node
19
+
20
+ def_delegator :stanza_node, :method_name, :stanza_name
21
+ def_delegator :stanza_node, :parent, :parent_node
22
+
23
+ def expression
24
+ stanza_node.expression
25
+ end
26
+
27
+ def_delegator :expression, :source
28
+
29
+ def stanza_group
30
+ Constants::STANZA_GROUP_HASH[stanza_name]
31
+ end
32
+
33
+ def same_group?(other)
34
+ stanza_group == other.stanza_group
35
+ end
36
+
37
+ def toplevel_stanza?
38
+ parent_node.parent.cask_block?
39
+ end
40
+
41
+ def ==(other)
42
+ self.class == other.class && stanza_node == other.stanza_node
43
+ end
44
+
45
+ alias_method :eql?, :==
46
+
47
+ Constants::STANZA_ORDER.each do |stanza_name|
48
+ class_eval <<-EOS, __FILE__, __LINE__
49
+ def #{stanza_name}?
50
+ stanza_name == :#{stanza_name}
51
+ end
52
+ EOS
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -2,30 +2,12 @@ module RuboCop
2
2
  module Cask
3
3
  # Common functionality for cops checking casks
4
4
  module CaskHelp
5
- include NodeHelp
6
-
7
- CASK_METHOD_NAMES = %i(cask test_cask)
8
-
9
- def cask?(block_node)
10
- block?(block_node) && cask_method?(method_node(block_node))
11
- end
12
-
13
- def cask_method?(method_node)
14
- CASK_METHOD_NAMES.include?(method_name(method_node))
15
- end
16
-
17
- def cask_children(node)
18
- node_children(node).select { |e| cask?(e) }
19
- end
20
-
21
5
  def on_block(block_node)
22
6
  super if defined? super
23
7
  return unless respond_to?(:on_cask)
24
- return unless cask?(block_node)
25
-
26
- method_node, _args, block_body = *block_node
8
+ return unless block_node.cask_block?
27
9
 
28
- on_cask(block_node, method_node, block_body)
10
+ on_cask(block_node)
29
11
  end
30
12
  end
31
13
  end
@@ -0,0 +1,54 @@
1
+ module RuboCop
2
+ module Cask
3
+ # Constants available globally for use in all Cask cops.
4
+ module Constants
5
+ CASK_METHOD_NAMES = [:cask, :test_cask].freeze
6
+
7
+ STANZA_GROUPS = [
8
+ [:version, :sha256],
9
+ [:url, :appcast, :name, :homepage, :license, :gpg],
10
+ [
11
+ :auto_updates,
12
+ :accessibility_access,
13
+ :conflicts_with,
14
+ :depends_on,
15
+ :container
16
+ ],
17
+ [
18
+ :suite,
19
+ :app,
20
+ :pkg,
21
+ :installer,
22
+ :binary,
23
+ :colorpicker,
24
+ :font,
25
+ :input_method,
26
+ :internet_plugin,
27
+ :prefpane,
28
+ :qlplugin,
29
+ :screen_saver,
30
+ :service,
31
+ :audio_unit_plugin,
32
+ :vst_plugin,
33
+ :artifact,
34
+ :stage_only
35
+ ],
36
+ [:preflight],
37
+ [:postflight],
38
+ [:uninstall_preflight],
39
+ [:uninstall_postflight],
40
+ [:uninstall],
41
+ [:zap],
42
+ [:caveats]
43
+ ].freeze
44
+
45
+ STANZA_GROUP_HASH =
46
+ STANZA_GROUPS.each_with_object({}) do |stanza_group, hash|
47
+ stanza_group.each { |stanza| hash[stanza] = stanza_group }
48
+ hash
49
+ end.freeze
50
+
51
+ STANZA_ORDER = STANZA_GROUPS.flatten.freeze
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,32 @@
1
+ module Astrolabe
2
+ # Extensions for Astrolabe's AST Node class
3
+ class Node
4
+ include RuboCop::Cask::Constants
5
+
6
+ def_matcher :method_node, '{$(send ...) (block $(send ...) ...)}'
7
+ def_matcher :block_args, '(block _ $_ _)'
8
+ def_matcher :block_body, '(block _ _ $_)'
9
+
10
+ def_matcher :key_node, '{(pair $_ _) (hash (pair $_ _) ...)}'
11
+ def_matcher :val_node, '{(pair _ $_) (hash (pair _ $_) ...)}'
12
+
13
+ def cask_block?
14
+ block_type? && method_node.cask_header?
15
+ end
16
+
17
+ def cask_header?
18
+ send_type? && CASK_METHOD_NAMES.include?(method_name)
19
+ end
20
+
21
+ def heredoc?
22
+ loc.is_a?(Parser::Source::Map::Heredoc)
23
+ end
24
+
25
+ def expression
26
+ base_expression = loc.expression
27
+ descendants.select(&:heredoc?).reduce(base_expression) do |expr, node|
28
+ expr.join(node.loc.heredoc_end)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ # Utility method extensions for String
2
+ class String
3
+ def undent
4
+ gsub(/^.{#{(slice(/^ +/) || '').length}}/, '')
5
+ end
6
+ end
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Cask
5
5
  # Version information for the Cask RuboCop plugin.
6
6
  module Version
7
- STRING = '0.1.0'
7
+ STRING = '0.2.0'
8
8
 
9
9
  def self.gem_version
10
10
  Gem::Version.new(STRING)
@@ -21,8 +21,8 @@ module RuboCop
21
21
 
22
22
  MESSAGE = 'Use `%s` instead of `%s`'
23
23
 
24
- def on_cask(_block_node, method_node, _block_body)
25
- @cask_header = cask_header(method_node)
24
+ def on_cask(cask_node)
25
+ @cask_header = cask_header(cask_node.method_node)
26
26
  return unless offense?
27
27
  offense
28
28
  end
@@ -40,7 +40,7 @@ module RuboCop
40
40
  def_delegators :@cask_header, :header_str, :preferred_header_str
41
41
 
42
42
  def cask_header(method_node)
43
- RuboCop::Cask::CaskHeader.new(method_node)
43
+ RuboCop::Cask::AST::CaskHeader.new(method_node)
44
44
  end
45
45
 
46
46
  def offense?
@@ -0,0 +1,96 @@
1
+ require 'forwardable'
2
+ require 'pry'
3
+
4
+ module RuboCop
5
+ module Cop
6
+ module Cask
7
+ # This cop checks that a cask's stanzas are grouped correctly.
8
+ # See https://github.com/caskroom/homebrew-cask/blob/master/CONTRIBUTING.md#stanza-order
9
+ # for more info.
10
+ class StanzaGrouping < Cop
11
+ extend Forwardable
12
+ include RuboCop::Cask::CaskHelp
13
+
14
+ MISSING_LINE_MSG = 'stanza groups should be separated by a single ' \
15
+ 'empty line'
16
+
17
+ EXTRA_LINE_MSG = 'stanzas within the same group should have no lines ' \
18
+ 'between them'
19
+
20
+ def on_cask(cask_node)
21
+ @cask_block = RuboCop::Cask::AST::CaskBlock.new(cask_node)
22
+ @line_ops = {}
23
+ add_offenses
24
+ end
25
+
26
+ def autocorrect(range)
27
+ lambda do |corrector|
28
+ case line_ops[range.line - 1]
29
+ when :insert
30
+ corrector.insert_before(range, "\n")
31
+ when :remove
32
+ corrector.remove(range)
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :cask_block, :line_ops
40
+ def_delegators :cask_block, :cask_node, :toplevel_stanzas
41
+
42
+ def add_offenses
43
+ toplevel_stanzas.each_cons(2) do |stanza, next_stanza|
44
+ next unless next_stanza
45
+ if missing_line_after?(stanza, next_stanza)
46
+ add_offense_missing_line(stanza)
47
+ elsif extra_line_after?(stanza, next_stanza)
48
+ add_offense_extra_line(stanza)
49
+ end
50
+ end
51
+ end
52
+
53
+ def missing_line_after?(stanza, next_stanza)
54
+ !(stanza.same_group?(next_stanza) ||
55
+ empty_line_after?(stanza))
56
+ end
57
+
58
+ def extra_line_after?(stanza, next_stanza)
59
+ stanza.same_group?(next_stanza) &&
60
+ empty_line_after?(stanza)
61
+ end
62
+
63
+ def empty_line_after?(stanza)
64
+ source_line_after(stanza).empty?
65
+ end
66
+
67
+ def source_line_after(stanza)
68
+ processed_source[index_of_line_after(stanza)]
69
+ end
70
+
71
+ def index_of_line_after(stanza)
72
+ stanza.expression.last_line
73
+ end
74
+
75
+ def add_offense_missing_line(stanza)
76
+ line_index = index_of_line_after(stanza)
77
+ line_ops[line_index] = :insert
78
+ add_offense(line_index, MISSING_LINE_MSG)
79
+ end
80
+
81
+ def add_offense_extra_line(stanza)
82
+ line_index = index_of_line_after(stanza)
83
+ line_ops[line_index] = :remove
84
+ add_offense(line_index, EXTRA_LINE_MSG)
85
+ end
86
+
87
+ def add_offense(line_index, msg)
88
+ line_length = [processed_source[line_index].size, 1].max
89
+ range = source_range(processed_source.buffer, line_index + 1, 0,
90
+ line_length)
91
+ super(range, range, msg)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end