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 +4 -4
- data/README.md +6 -6
- data/config/default.yml +8 -0
- data/lib/rubocop-cask.rb +8 -3
- data/lib/rubocop/cask/ast/cask_block.rb +61 -0
- data/lib/rubocop/cask/ast/cask_header.rb +61 -0
- data/lib/rubocop/cask/ast/stanza.rb +57 -0
- data/lib/rubocop/cask/cask_help.rb +2 -20
- data/lib/rubocop/cask/constants.rb +54 -0
- data/lib/rubocop/cask/extend/astrolabe/node.rb +32 -0
- data/lib/rubocop/cask/extend/string.rb +6 -0
- data/lib/rubocop/cask/version.rb +1 -1
- data/lib/rubocop/cop/cask/no_dsl_version.rb +3 -3
- data/lib/rubocop/cop/cask/stanza_grouping.rb +96 -0
- data/lib/rubocop/cop/cask/stanza_order.rb +51 -0
- data/spec/rubocop/cop/cask/no_dsl_version_spec.rb +56 -77
- data/spec/rubocop/cop/cask/stanza_grouping_spec.rb +194 -0
- data/spec/rubocop/cop/cask/stanza_order_spec.rb +213 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/support/cop_shared_examples.rb +33 -0
- metadata +16 -5
- data/lib/rubocop/cask/cask_block.rb +0 -20
- data/lib/rubocop/cask/cask_header.rb +0 -59
- data/lib/rubocop/cask/node_help.rb +0 -81
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eedae76807564012ce59a4b4a81af9751a851d2a
|
4
|
+
data.tar.gz: f7d783a06b07aaa8ddb2fdeec8f1ad9969851c40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/
|
5
|
-
[![Build Status](https://travis-ci.org/
|
6
|
-
[![Coverage Status](https://img.shields.io/codeclimate/coverage/github/
|
7
|
-
[![Code Climate](https://codeclimate.com/github/
|
8
|
-
[![Inline docs](http://inch-ci.org/github/
|
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
|
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/
|
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
|
25
|
-
|
26
|
-
method_node, _args, block_body = *block_node
|
8
|
+
return unless block_node.cask_block?
|
27
9
|
|
28
|
-
on_cask(block_node
|
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
|
data/lib/rubocop/cask/version.rb
CHANGED
@@ -21,8 +21,8 @@ module RuboCop
|
|
21
21
|
|
22
22
|
MESSAGE = 'Use `%s` instead of `%s`'
|
23
23
|
|
24
|
-
def on_cask(
|
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
|