rubocop-cask 0.1.0 → 0.2.0

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