Import rubocop-cask (with clean brew style)
This commit is contained in:
parent
376eac2b8d
commit
491ceb6c4c
@ -15,3 +15,5 @@ require "rubocops/options"
|
||||
require "rubocops/urls"
|
||||
require "rubocops/lines"
|
||||
require "rubocops/class"
|
||||
|
||||
require "rubocops/rubocop-cask"
|
||||
|
||||
74
Library/Homebrew/rubocops/cask/ast/cask_block.rb
Normal file
74
Library/Homebrew/rubocops/cask/ast/cask_block.rb
Normal file
@ -0,0 +1,74 @@
|
||||
require "forwardable"
|
||||
|
||||
module RuboCop
|
||||
module Cask
|
||||
module AST
|
||||
# This class wraps the AST block node that represents the entire cask
|
||||
# definition. It includes various helper methods to aid cops in their
|
||||
# analysis.
|
||||
class CaskBlock
|
||||
extend Forwardable
|
||||
|
||||
def initialize(block_node, comments)
|
||||
@block_node = block_node
|
||||
@comments = comments
|
||||
end
|
||||
|
||||
attr_reader :block_node, :comments
|
||||
|
||||
alias cask_node block_node
|
||||
|
||||
def_delegator :cask_node, :block_body, :cask_body
|
||||
|
||||
def header
|
||||
@header ||= CaskHeader.new(cask_node.method_node)
|
||||
end
|
||||
|
||||
def stanzas
|
||||
return [] unless cask_body
|
||||
|
||||
@stanzas ||= cask_body.each_node
|
||||
.select(&:stanza?)
|
||||
.map { |node| Stanza.new(node, stanza_comments(node)) }
|
||||
end
|
||||
|
||||
def toplevel_stanzas
|
||||
@toplevel_stanzas ||= stanzas.select(&:toplevel_stanza?)
|
||||
end
|
||||
|
||||
def sorted_toplevel_stanzas
|
||||
@sorted_toplevel_stanzas ||= sort_stanzas(toplevel_stanzas)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sort_stanzas(stanzas)
|
||||
stanzas.sort do |s1, s2|
|
||||
i1 = stanza_order_index(s1)
|
||||
i2 = stanza_order_index(s2)
|
||||
if i1 == i2
|
||||
i1 = stanzas.index(s1)
|
||||
i2 = stanzas.index(s2)
|
||||
end
|
||||
i1 - i2
|
||||
end
|
||||
end
|
||||
|
||||
def stanza_order_index(stanza)
|
||||
Constants::STANZA_ORDER.index(stanza.stanza_name)
|
||||
end
|
||||
|
||||
def stanza_comments(stanza_node)
|
||||
stanza_node.each_node.reduce([]) do |comments, node|
|
||||
comments | comments_hash[node.loc]
|
||||
end
|
||||
end
|
||||
|
||||
def comments_hash
|
||||
@comments_hash ||= Parser::Source::Comment
|
||||
.associate_locations(cask_node, comments)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
Library/Homebrew/rubocops/cask/ast/cask_header.rb
Normal file
43
Library/Homebrew/rubocops/cask/ast/cask_header.rb
Normal file
@ -0,0 +1,43 @@
|
||||
module RuboCop
|
||||
module Cask
|
||||
module AST
|
||||
# This class wraps the AST method node that represents the cask header. It
|
||||
# includes various helper methods to aid cops in their analysis.
|
||||
class CaskHeader
|
||||
def initialize(method_node)
|
||||
@method_node = method_node
|
||||
end
|
||||
|
||||
attr_reader :method_node
|
||||
|
||||
def dsl_version?
|
||||
hash_node
|
||||
end
|
||||
|
||||
def header_str
|
||||
@header_str ||= source_range.source
|
||||
end
|
||||
|
||||
def source_range
|
||||
@source_range ||= method_node.loc.expression
|
||||
end
|
||||
|
||||
def preferred_header_str
|
||||
"cask '#{cask_token}'"
|
||||
end
|
||||
|
||||
def cask_token
|
||||
@cask_token ||= pair_node.val_node.children.first
|
||||
end
|
||||
|
||||
def hash_node
|
||||
@hash_node ||= method_node.each_child_node(:hash).first
|
||||
end
|
||||
|
||||
def pair_node
|
||||
@pair_node ||= hash_node.each_child_node(:pair).first
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
66
Library/Homebrew/rubocops/cask/ast/stanza.rb
Normal file
66
Library/Homebrew/rubocops/cask/ast/stanza.rb
Normal file
@ -0,0 +1,66 @@
|
||||
require "forwardable"
|
||||
|
||||
module RuboCop
|
||||
module Cask
|
||||
module AST
|
||||
# This class wraps the AST send/block node that encapsulates the method
|
||||
# call that comprises the stanza. It includes various helper methods to
|
||||
# aid cops in their analysis.
|
||||
class Stanza
|
||||
extend Forwardable
|
||||
|
||||
def initialize(method_node, comments)
|
||||
@method_node = method_node
|
||||
@comments = comments
|
||||
end
|
||||
|
||||
attr_reader :method_node, :comments
|
||||
|
||||
alias stanza_node method_node
|
||||
|
||||
def_delegator :stanza_node, :method_name, :stanza_name
|
||||
def_delegator :stanza_node, :parent, :parent_node
|
||||
|
||||
def source_range
|
||||
stanza_node.expression
|
||||
end
|
||||
|
||||
def source_range_with_comments
|
||||
comments.reduce(source_range) do |range, comment|
|
||||
range.join(comment.loc.expression)
|
||||
end
|
||||
end
|
||||
|
||||
def_delegator :source_range, :source
|
||||
def_delegator :source_range_with_comments, :source,
|
||||
:source_with_comments
|
||||
|
||||
def stanza_group
|
||||
Constants::STANZA_GROUP_HASH[stanza_name]
|
||||
end
|
||||
|
||||
def same_group?(other)
|
||||
stanza_group == other.stanza_group
|
||||
end
|
||||
|
||||
def toplevel_stanza?
|
||||
parent_node.cask_block? || parent_node.parent.cask_block?
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self.class == other.class && stanza_node == other.stanza_node
|
||||
end
|
||||
|
||||
alias eql? ==
|
||||
|
||||
Constants::STANZA_ORDER.each do |stanza_name|
|
||||
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||
def #{stanza_name}?
|
||||
stanza_name == :#{stanza_name}
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
51
Library/Homebrew/rubocops/cask/constants/stanza.rb
Normal file
51
Library/Homebrew/rubocops/cask/constants/stanza.rb
Normal file
@ -0,0 +1,51 @@
|
||||
module RuboCop
|
||||
module Cask
|
||||
# Constants available globally for use in all Cask cops.
|
||||
module Constants
|
||||
STANZA_GROUPS = [
|
||||
[:version, :sha256],
|
||||
[:url, :appcast, :name, :homepage],
|
||||
[
|
||||
:auto_updates,
|
||||
:conflicts_with,
|
||||
:depends_on,
|
||||
:container,
|
||||
],
|
||||
[
|
||||
:suite,
|
||||
:app,
|
||||
:pkg,
|
||||
:installer,
|
||||
:binary,
|
||||
:colorpicker,
|
||||
:dictionary,
|
||||
:font,
|
||||
:input_method,
|
||||
:internet_plugin,
|
||||
:prefpane,
|
||||
:qlplugin,
|
||||
:screen_saver,
|
||||
:service,
|
||||
:audio_unit_plugin,
|
||||
:vst_plugin,
|
||||
:artifact,
|
||||
:stage_only,
|
||||
],
|
||||
[:preflight],
|
||||
[:postflight],
|
||||
[:uninstall_preflight],
|
||||
[:uninstall_postflight],
|
||||
[:uninstall],
|
||||
[:zap],
|
||||
[:caveats],
|
||||
].freeze
|
||||
|
||||
STANZA_GROUP_HASH =
|
||||
STANZA_GROUPS.each_with_object({}) do |stanza_group, hash|
|
||||
stanza_group.each { |stanza| hash[stanza] = stanza_group }
|
||||
end.freeze
|
||||
|
||||
STANZA_ORDER = STANZA_GROUPS.flatten.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
32
Library/Homebrew/rubocops/cask/extend/node.rb
Normal file
32
Library/Homebrew/rubocops/cask/extend/node.rb
Normal file
@ -0,0 +1,32 @@
|
||||
module RuboCop
|
||||
module AST
|
||||
# Extensions for RuboCop's AST Node class
|
||||
class Node
|
||||
include RuboCop::Cask::Constants
|
||||
|
||||
def_node_matcher :method_node, "{$(send ...) (block $(send ...) ...)}"
|
||||
def_node_matcher :block_args, "(block _ $_ _)"
|
||||
def_node_matcher :block_body, "(block _ _ $_)"
|
||||
|
||||
def_node_matcher :key_node, "{(pair $_ _) (hash (pair $_ _) ...)}"
|
||||
def_node_matcher :val_node, "{(pair _ $_) (hash (pair _ $_) ...)}"
|
||||
|
||||
def_node_matcher :cask_block?, "(block (send nil? :cask _) args ...)"
|
||||
|
||||
def stanza?
|
||||
(send_type? || block_type?) && STANZA_ORDER.include?(method_name)
|
||||
end
|
||||
|
||||
def heredoc?
|
||||
loc.is_a?(Parser::Source::Map::Heredoc)
|
||||
end
|
||||
|
||||
def expression
|
||||
base_expression = loc.expression
|
||||
descendants.select(&:heredoc?).reduce(base_expression) do |expr, node|
|
||||
expr.join(node.loc.heredoc_end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
6
Library/Homebrew/rubocops/cask/extend/string.rb
Normal file
6
Library/Homebrew/rubocops/cask/extend/string.rb
Normal file
@ -0,0 +1,6 @@
|
||||
# Utility method extensions for String
|
||||
class String
|
||||
def undent
|
||||
gsub(/^.{#{(slice(/^ +/) || '').length}}/, "")
|
||||
end
|
||||
end
|
||||
168
Library/Homebrew/rubocops/cask/homepage_matches_url.rb
Normal file
168
Library/Homebrew/rubocops/cask/homepage_matches_url.rb
Normal file
@ -0,0 +1,168 @@
|
||||
require "forwardable"
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Cask
|
||||
# This cop checks that a cask's homepage matches the download url,
|
||||
# or if it doesn't, checks if a comment in the form
|
||||
# `# example.com was verified as official when first introduced to the cask`
|
||||
# is present.
|
||||
class HomepageMatchesUrl < Cop # rubocop:disable Metrics/ClassLength
|
||||
extend Forwardable
|
||||
include CaskHelp
|
||||
|
||||
REFERENCE_URL =
|
||||
"https://github.com/Homebrew/homebrew-cask/blob/master/doc/" \
|
||||
"cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-a-comment".freeze
|
||||
|
||||
COMMENT_FORMAT = /# [^ ]+ was verified as official when first introduced to the cask/.freeze
|
||||
|
||||
MSG_NO_MATCH = "`%{url}` does not match `%{full_url}`".freeze
|
||||
|
||||
MSG_MISSING = "`%{domain}` does not match `%{homepage}`, a comment has to be added " \
|
||||
"above the `url` stanza. For details, see " + REFERENCE_URL
|
||||
|
||||
MSG_WRONG_FORMAT = "`%{comment}` does not match the expected comment format. " \
|
||||
"For details, see " + REFERENCE_URL
|
||||
|
||||
MSG_UNNECESSARY = "The URL's domain `%{domain}` matches the homepage `%{homepage}`, " \
|
||||
"the comment above the `url` stanza is unnecessary".freeze
|
||||
|
||||
def on_cask(cask_block)
|
||||
@cask_block = cask_block
|
||||
return unless homepage_stanza
|
||||
|
||||
add_offenses
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :cask_block
|
||||
def_delegators :cask_block, :cask_node, :toplevel_stanzas,
|
||||
:sorted_toplevel_stanzas
|
||||
|
||||
def add_offenses
|
||||
toplevel_stanzas.select(&:url?).each do |url|
|
||||
next if add_offense_unnecessary_comment(url)
|
||||
next if add_offense_missing_comment(url)
|
||||
next if add_offense_no_match(url)
|
||||
next if add_offense_wrong_format(url)
|
||||
end
|
||||
end
|
||||
|
||||
def add_offense_unnecessary_comment(stanza)
|
||||
return unless comment?(stanza)
|
||||
return unless url_match_homepage?(stanza)
|
||||
return unless comment_matches_format?(stanza)
|
||||
return unless comment_matches_url?(stanza)
|
||||
|
||||
comment = comment(stanza).loc.expression
|
||||
add_offense(comment,
|
||||
location: comment,
|
||||
message: format(MSG_UNNECESSARY, domain: domain(stanza), homepage: homepage))
|
||||
end
|
||||
|
||||
def add_offense_missing_comment(stanza)
|
||||
return if url_match_homepage?(stanza)
|
||||
return if !url_match_homepage?(stanza) && comment?(stanza)
|
||||
|
||||
range = stanza.source_range
|
||||
url_domain = domain(stanza)
|
||||
add_offense(range, location: range, message: format(MSG_MISSING, domain: url_domain, homepage: homepage))
|
||||
end
|
||||
|
||||
def add_offense_no_match(stanza)
|
||||
return if url_match_homepage?(stanza)
|
||||
return unless comment?(stanza)
|
||||
return if !url_match_homepage?(stanza) && comment_matches_url?(stanza)
|
||||
|
||||
comment = comment(stanza).loc.expression
|
||||
add_offense(comment,
|
||||
location: comment,
|
||||
message: format(MSG_NO_MATCH, url: url_from_comment(stanza), full_url: full_url(stanza)))
|
||||
end
|
||||
|
||||
def add_offense_wrong_format(stanza)
|
||||
return if url_match_homepage?(stanza)
|
||||
return unless comment?(stanza)
|
||||
return if comment_matches_format?(stanza)
|
||||
|
||||
comment = comment(stanza).loc.expression
|
||||
add_offense(comment,
|
||||
location: comment,
|
||||
message: format(MSG_WRONG_FORMAT, comment: comment(stanza).text))
|
||||
end
|
||||
|
||||
def comment?(stanza)
|
||||
!stanza.comments.empty?
|
||||
end
|
||||
|
||||
def comment(stanza)
|
||||
stanza.comments.last
|
||||
end
|
||||
|
||||
def comment_matches_format?(stanza)
|
||||
comment(stanza).text =~ COMMENT_FORMAT
|
||||
end
|
||||
|
||||
def url_from_comment(stanza)
|
||||
comment(stanza).text
|
||||
.sub(/[^ ]*# ([^ ]+) .*/, '\1')
|
||||
end
|
||||
|
||||
def comment_matches_url?(stanza)
|
||||
full_url(stanza).include?(url_from_comment(stanza))
|
||||
end
|
||||
|
||||
def strip_url_scheme(url)
|
||||
url.sub(%r{^.*://(www\.)?}, "")
|
||||
end
|
||||
|
||||
def domain(stanza)
|
||||
strip_url_scheme(extract_url(stanza)).gsub(%r{^([^/]+).*}, '\1')
|
||||
end
|
||||
|
||||
def extract_url(stanza)
|
||||
string = stanza.stanza_node.children[2]
|
||||
return string.str_content if string.str_type?
|
||||
|
||||
string.to_s.gsub(%r{.*"([a-z0-9]+\:\/\/[^"]+)".*}m, '\1')
|
||||
end
|
||||
|
||||
def url_match_homepage?(stanza)
|
||||
host = extract_url(stanza).downcase
|
||||
host_uri = URI(remove_non_ascii(host))
|
||||
host = if host.match?(/:\d/) && host_uri.port != 80
|
||||
"#{host_uri.host}:#{host_uri.port}"
|
||||
else
|
||||
host_uri.host
|
||||
end
|
||||
home = homepage.downcase
|
||||
if (split_host = host.split(".")).length >= 3
|
||||
host = split_host[-2..-1].join(".")
|
||||
end
|
||||
if (split_home = homepage.split(".")).length >= 3
|
||||
home = split_home[-2..-1].join(".")
|
||||
end
|
||||
host == home
|
||||
end
|
||||
|
||||
def full_url(stanza)
|
||||
strip_url_scheme(extract_url(stanza))
|
||||
end
|
||||
|
||||
def homepage
|
||||
URI(remove_non_ascii(extract_url(homepage_stanza))).host
|
||||
end
|
||||
|
||||
def homepage_stanza
|
||||
toplevel_stanzas.find(&:homepage?)
|
||||
end
|
||||
|
||||
def remove_non_ascii(string)
|
||||
string.gsub(/\P{ASCII}/, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,38 @@
|
||||
require "forwardable"
|
||||
require "uri"
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Cask
|
||||
# This cop checks that a cask's homepage ends with a slash
|
||||
# if it does not have a path component.
|
||||
class HomepageUrlTrailingSlash < Cop
|
||||
include OnHomepageStanza
|
||||
|
||||
MSG_NO_SLASH = "'%{url}' must have a slash after the domain.".freeze
|
||||
|
||||
def on_homepage_stanza(stanza)
|
||||
url_node = stanza.stanza_node.first_argument
|
||||
url = url_node.str_content
|
||||
|
||||
return if url !~ %r{^.+://[^/]+$}
|
||||
|
||||
add_offense(url_node, location: :expression,
|
||||
message: format(MSG_NO_SLASH, url: url))
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
domain = URI(node.str_content).host
|
||||
|
||||
# This also takes URLs like 'https://example.org?path'
|
||||
# and 'https://example.org#path' into account.
|
||||
corrected_source = node.source.sub("://#{domain}", "://#{domain}/")
|
||||
|
||||
lambda do |corrector|
|
||||
corrector.replace(node.source_range, corrected_source)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
18
Library/Homebrew/rubocops/cask/mixin/cask_help.rb
Normal file
18
Library/Homebrew/rubocops/cask/mixin/cask_help.rb
Normal file
@ -0,0 +1,18 @@
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Cask
|
||||
# Common functionality for cops checking casks
|
||||
module CaskHelp
|
||||
def on_block(block_node)
|
||||
super if defined? super
|
||||
return unless respond_to?(:on_cask)
|
||||
return unless block_node.cask_block?
|
||||
|
||||
comments = processed_source.comments
|
||||
cask_block = RuboCop::Cask::AST::CaskBlock.new(block_node, comments)
|
||||
on_cask(cask_block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
25
Library/Homebrew/rubocops/cask/mixin/on_homepage_stanza.rb
Normal file
25
Library/Homebrew/rubocops/cask/mixin/on_homepage_stanza.rb
Normal file
@ -0,0 +1,25 @@
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Cask
|
||||
# Common functionality for checking homepage stanzas.
|
||||
module OnHomepageStanza
|
||||
extend Forwardable
|
||||
include CaskHelp
|
||||
|
||||
def on_cask(cask_block)
|
||||
@cask_block = cask_block
|
||||
|
||||
toplevel_stanzas.select(&:homepage?).each do |stanza|
|
||||
on_homepage_stanza(stanza)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :cask_block
|
||||
def_delegators :cask_block,
|
||||
:toplevel_stanzas
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
62
Library/Homebrew/rubocops/cask/no_dsl_version.rb
Normal file
62
Library/Homebrew/rubocops/cask/no_dsl_version.rb
Normal file
@ -0,0 +1,62 @@
|
||||
require "forwardable"
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Cask
|
||||
# Do not use the deprecated DSL version syntax in your cask header.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# cask :v1 => 'foo' do
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# cask 'foo' do
|
||||
# ...
|
||||
# end
|
||||
class NoDslVersion < Cop
|
||||
extend Forwardable
|
||||
include CaskHelp
|
||||
|
||||
MESSAGE = "Use `%{preferred}` instead of `%{current}`".freeze
|
||||
|
||||
def on_cask(cask_block)
|
||||
@cask_header = cask_block.header
|
||||
return unless offense?
|
||||
|
||||
offense
|
||||
end
|
||||
|
||||
def autocorrect(method_node)
|
||||
@cask_header = cask_header(method_node)
|
||||
lambda do |corrector|
|
||||
corrector.replace(header_range, preferred_header_str)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def_delegator :@cask_header, :source_range, :header_range
|
||||
def_delegators :@cask_header, :header_str, :preferred_header_str
|
||||
|
||||
def cask_header(method_node)
|
||||
RuboCop::Cask::AST::CaskHeader.new(method_node)
|
||||
end
|
||||
|
||||
def offense?
|
||||
@cask_header.dsl_version?
|
||||
end
|
||||
|
||||
def offense
|
||||
add_offense(@cask_header.method_node, location: header_range,
|
||||
message: error_msg)
|
||||
end
|
||||
|
||||
def error_msg
|
||||
format(MESSAGE, preferred: preferred_header_str, current: header_str)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
97
Library/Homebrew/rubocops/cask/stanza_grouping.rb
Normal file
97
Library/Homebrew/rubocops/cask/stanza_grouping.rb
Normal file
@ -0,0 +1,97 @@
|
||||
require "forwardable"
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Cask
|
||||
# This cop checks that a cask's stanzas are grouped correctly.
|
||||
# See https://github.com/Homebrew/homebrew-cask/blob/master/CONTRIBUTING.md#stanza-order
|
||||
# for more info.
|
||||
class StanzaGrouping < Cop
|
||||
extend Forwardable
|
||||
include CaskHelp
|
||||
include RangeHelp
|
||||
|
||||
MISSING_LINE_MSG = "stanza groups should be separated by a single " \
|
||||
"empty line".freeze
|
||||
|
||||
EXTRA_LINE_MSG = "stanzas within the same group should have no lines " \
|
||||
"between them".freeze
|
||||
|
||||
def on_cask(cask_block)
|
||||
@cask_block = cask_block
|
||||
@line_ops = {}
|
||||
add_offenses
|
||||
end
|
||||
|
||||
def autocorrect(range)
|
||||
lambda do |corrector|
|
||||
case line_ops[range.line - 1]
|
||||
when :insert
|
||||
corrector.insert_before(range, "\n")
|
||||
when :remove
|
||||
corrector.remove(range)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :cask_block, :line_ops
|
||||
def_delegators :cask_block, :cask_node, :toplevel_stanzas
|
||||
|
||||
def add_offenses
|
||||
toplevel_stanzas.each_cons(2) do |stanza, next_stanza|
|
||||
next unless next_stanza
|
||||
|
||||
if missing_line_after?(stanza, next_stanza)
|
||||
add_offense_missing_line(stanza)
|
||||
elsif extra_line_after?(stanza, next_stanza)
|
||||
add_offense_extra_line(stanza)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def missing_line_after?(stanza, next_stanza)
|
||||
!(stanza.same_group?(next_stanza) ||
|
||||
empty_line_after?(stanza))
|
||||
end
|
||||
|
||||
def extra_line_after?(stanza, next_stanza)
|
||||
stanza.same_group?(next_stanza) &&
|
||||
empty_line_after?(stanza)
|
||||
end
|
||||
|
||||
def empty_line_after?(stanza)
|
||||
source_line_after(stanza).empty?
|
||||
end
|
||||
|
||||
def source_line_after(stanza)
|
||||
processed_source[index_of_line_after(stanza)]
|
||||
end
|
||||
|
||||
def index_of_line_after(stanza)
|
||||
stanza.source_range.last_line
|
||||
end
|
||||
|
||||
def add_offense_missing_line(stanza)
|
||||
line_index = index_of_line_after(stanza)
|
||||
line_ops[line_index] = :insert
|
||||
add_offense(line_index, message: MISSING_LINE_MSG)
|
||||
end
|
||||
|
||||
def add_offense_extra_line(stanza)
|
||||
line_index = index_of_line_after(stanza)
|
||||
line_ops[line_index] = :remove
|
||||
add_offense(line_index, message: EXTRA_LINE_MSG)
|
||||
end
|
||||
|
||||
def add_offense(line_index, message:)
|
||||
line_length = [processed_source[line_index].size, 1].max
|
||||
range = source_range(processed_source.buffer, line_index + 1, 0,
|
||||
line_length)
|
||||
super(range, location: range, message: message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
53
Library/Homebrew/rubocops/cask/stanza_order.rb
Normal file
53
Library/Homebrew/rubocops/cask/stanza_order.rb
Normal file
@ -0,0 +1,53 @@
|
||||
require "forwardable"
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Cask
|
||||
# This cop checks that a cask's stanzas are ordered correctly.
|
||||
# See https://github.com/Homebrew/homebrew-cask/blob/master/CONTRIBUTING.md#stanza-order
|
||||
# for more info.
|
||||
class StanzaOrder < Cop
|
||||
extend Forwardable
|
||||
include CaskHelp
|
||||
|
||||
MESSAGE = "`%{stanza}` stanza out of order".freeze
|
||||
|
||||
def on_cask(cask_block)
|
||||
@cask_block = cask_block
|
||||
add_offenses
|
||||
end
|
||||
|
||||
def autocorrect(stanza)
|
||||
lambda do |corrector|
|
||||
correct_stanza_index = toplevel_stanzas.index(stanza)
|
||||
correct_stanza = sorted_toplevel_stanzas[correct_stanza_index]
|
||||
corrector.replace(stanza.source_range_with_comments,
|
||||
correct_stanza.source_with_comments)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :cask_block
|
||||
def_delegators :cask_block, :cask_node, :toplevel_stanzas,
|
||||
:sorted_toplevel_stanzas
|
||||
|
||||
def add_offenses
|
||||
offending_stanzas.each do |stanza|
|
||||
message = format(MESSAGE, stanza: stanza.stanza_name)
|
||||
add_offense(stanza, location: stanza.source_range_with_comments,
|
||||
message: message)
|
||||
end
|
||||
end
|
||||
|
||||
def offending_stanzas
|
||||
stanza_pairs = toplevel_stanzas.zip(sorted_toplevel_stanzas)
|
||||
stanza_pairs.each_with_object([]) do |stanza_pair, offending_stanzas|
|
||||
stanza, sorted_stanza = *stanza_pair
|
||||
offending_stanzas << stanza unless stanza == sorted_stanza
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,28 +1,16 @@
|
||||
require 'rubocop'
|
||||
require "rubocop"
|
||||
|
||||
require 'rubocops/cask/constants/cask_method_names'
|
||||
require 'rubocops/cask/constants/stanza'
|
||||
require "rubocops/cask/constants/stanza"
|
||||
|
||||
require 'rubocops/cask/ast/stanza'
|
||||
require 'rubocops/cask/ast/cask_header'
|
||||
require 'rubocops/cask/ast/cask_block'
|
||||
require 'rubocops/cask/extend/string'
|
||||
require 'rubocops/cask/extend/node'
|
||||
require 'rubocops/cask/mixin/cask_help'
|
||||
require 'rubocops/cask/mixin/on_homepage_stanza'
|
||||
require 'rubocops/cask/homepage_matches_url'
|
||||
require 'rubocops/cask/homepage_url_trailing_slash'
|
||||
require 'rubocops/cask/no_dsl_version'
|
||||
require 'rubocops/cask/stanza_order'
|
||||
require 'rubocops/cask/stanza_grouping'
|
||||
|
||||
module RuboCop
|
||||
module Cask
|
||||
DEFAULT_CONFIG = File.expand_path('cask/config/default.yml', __dir__)
|
||||
end
|
||||
|
||||
ConfigLoader.default_configuration = ConfigLoader.merge_with_default(
|
||||
ConfigLoader.load_file(Cask::DEFAULT_CONFIG),
|
||||
Cask::DEFAULT_CONFIG
|
||||
)
|
||||
end
|
||||
require "rubocops/cask/ast/stanza"
|
||||
require "rubocops/cask/ast/cask_header"
|
||||
require "rubocops/cask/ast/cask_block"
|
||||
require "rubocops/cask/extend/string"
|
||||
require "rubocops/cask/extend/node"
|
||||
require "rubocops/cask/mixin/cask_help"
|
||||
require "rubocops/cask/mixin/on_homepage_stanza"
|
||||
require "rubocops/cask/homepage_matches_url"
|
||||
require "rubocops/cask/homepage_url_trailing_slash"
|
||||
require "rubocops/cask/no_dsl_version"
|
||||
require "rubocops/cask/stanza_order"
|
||||
require "rubocops/cask/stanza_grouping"
|
||||
|
||||
213
Library/Homebrew/test/rubocops/cask/homepage_matches_url_spec.rb
Normal file
213
Library/Homebrew/test/rubocops/cask/homepage_matches_url_spec.rb
Normal file
@ -0,0 +1,213 @@
|
||||
require "rubocops/rubocop-cask"
|
||||
require "test/rubocops/cask/shared_examples/cask_cop"
|
||||
|
||||
describe RuboCop::Cop::Cask::HomepageMatchesUrl do
|
||||
include CaskCop
|
||||
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
context "when the url matches the homepage and there is no comment" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
homepage 'https://foo.example.com'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when the url matches the homepage and the url stanza has " \
|
||||
"a referrer and no interpolation" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
url 'https://foo.example.com/foo.zip',
|
||||
referrer: 'https://example.com/foo/'
|
||||
homepage 'https://foo.example.com'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when the url matches the homepage and the url stanza has " \
|
||||
"a referrer and interpolation" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version '1.8.0_72,8.13.0.5'
|
||||
url "https://foo.example.com/foo-\#{version.after_comma}-\#{version.minor}.\#{version.patch}.\#{version.before_comma.sub(\%r{.*_}, '')}.zip",
|
||||
referrer: 'https://example.com/foo/'
|
||||
homepage 'https://foo.example.com'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when the url matches the homepage but there is a comment " \
|
||||
"which does not match the url" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
# this is just a comment with information
|
||||
url 'https://example.com/foo.zip'
|
||||
homepage 'https://example.com'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when the url matches the homepage " \
|
||||
"but there is a comment matching the url" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
# foo.example.com was verified as official when first introduced to the cask
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
homepage 'https://foo.example.com'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:expected_offenses) do
|
||||
[{
|
||||
message: "The URL's domain `foo.example.com` matches the homepage " \
|
||||
"`foo.example.com`, the comment above the `url` stanza is " \
|
||||
"unnecessary",
|
||||
severity: :convention,
|
||||
line: 2,
|
||||
column: 2,
|
||||
source: "# foo.example.com was verified as official when " \
|
||||
"first introduced to the cask",
|
||||
}]
|
||||
end
|
||||
|
||||
include_examples "reports offenses"
|
||||
end
|
||||
|
||||
context "when the url does not match the homepage" do
|
||||
context "when there is a comment matching the url " \
|
||||
"but not matching the expected format" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
# example.com was verified as official
|
||||
url 'https://example.com/foo.zip'
|
||||
homepage 'https://foo.example.org'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:expected_offenses) do
|
||||
[{
|
||||
message: "`# example.com was verified as official` does not " \
|
||||
"match the expected comment format. For details, see " \
|
||||
"https://github.com/Homebrew/homebrew-cask/blob/master/doc/" \
|
||||
"cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-a-comment",
|
||||
severity: :convention,
|
||||
line: 2,
|
||||
column: 2,
|
||||
source: "# example.com was verified as official",
|
||||
}]
|
||||
end
|
||||
|
||||
include_examples "reports offenses"
|
||||
end
|
||||
|
||||
context "when there is a comment matching the url " \
|
||||
"and does not have slashes" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
# example.com was verified as official when first introduced to the cask
|
||||
url 'https://example.com/foo.zip'
|
||||
homepage 'https://foo.example.org'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when there is a comment matching the url and has slashes" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
# example.com/vendor/app was verified as official when first introduced to the cask
|
||||
url 'https://downloads.example.com/vendor/app/foo.zip'
|
||||
homepage 'https://vendor.example.org/app/'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when there is a comment which does not match the url" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
# example.com was verified as official when first introduced to the cask
|
||||
url 'https://example.org/foo.zip'
|
||||
homepage 'https://foo.example.com'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:expected_offenses) do
|
||||
[{
|
||||
message: "`example.com` does not match `example.org/foo.zip`",
|
||||
severity: :convention,
|
||||
line: 2,
|
||||
column: 2,
|
||||
source: "# example.com was verified as official when " \
|
||||
"first introduced to the cask",
|
||||
}]
|
||||
end
|
||||
|
||||
include_examples "reports offenses"
|
||||
end
|
||||
|
||||
context "when the comment is missing" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
url 'https://example.com/foo.zip'
|
||||
homepage 'https://example.org'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:expected_offenses) do
|
||||
[{
|
||||
message: "`example.com` does not match `example.org`, a comment " \
|
||||
"has to be added above the `url` stanza. For details, see " \
|
||||
"https://github.com/Homebrew/homebrew-cask/blob/master/doc/" \
|
||||
"cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-a-comment",
|
||||
severity: :convention,
|
||||
line: 2,
|
||||
column: 2,
|
||||
source: "url 'https://example.com/foo.zip'",
|
||||
}]
|
||||
end
|
||||
|
||||
include_examples "reports offenses"
|
||||
end
|
||||
end
|
||||
|
||||
context "when there is no homepage" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
url 'https://example.com/foo.zip'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,63 @@
|
||||
require "rubocops/rubocop-cask"
|
||||
require "test/rubocops/cask/shared_examples/cask_cop"
|
||||
|
||||
describe RuboCop::Cop::Cask::HomepageUrlTrailingSlash do
|
||||
include CaskCop
|
||||
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
context "when the homepage url ends with a slash" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
homepage 'https://foo.example.com/'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when the homepage url does not end with a slash but has a path" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
homepage 'https://foo.example.com/path'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when the homepage url does not end with a slash and has no path" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
homepage 'https://foo.example.com'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
homepage 'https://foo.example.com/'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:expected_offenses) do
|
||||
[{
|
||||
message: "'https://foo.example.com' must have a slash "\
|
||||
"after the domain.",
|
||||
severity: :convention,
|
||||
line: 2,
|
||||
column: 11,
|
||||
source: "'https://foo.example.com'",
|
||||
}]
|
||||
end
|
||||
|
||||
include_examples "reports offenses"
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
end
|
||||
36
Library/Homebrew/test/rubocops/cask/no_dsl_version_spec.rb
Normal file
36
Library/Homebrew/test/rubocops/cask/no_dsl_version_spec.rb
Normal file
@ -0,0 +1,36 @@
|
||||
require "rubocops/rubocop-cask"
|
||||
require "test/rubocops/cask/shared_examples/cask_cop"
|
||||
|
||||
describe RuboCop::Cop::Cask::NoDslVersion do
|
||||
include CaskCop
|
||||
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
context "with header method `cask`" do
|
||||
let(:header_method) { "cask" }
|
||||
|
||||
context "with no dsl version" do
|
||||
let(:source) { "cask 'foo' do; end" }
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "with dsl version" do
|
||||
let(:source) { "cask :v1 => 'foo' do; end" }
|
||||
let(:correct_source) { "cask 'foo' do; end" }
|
||||
let(:expected_offenses) do
|
||||
[{
|
||||
message: "Use `cask 'foo'` instead of `cask :v1 => 'foo'`",
|
||||
severity: :convention,
|
||||
line: 1,
|
||||
column: 0,
|
||||
source: "cask :v1 => 'foo'",
|
||||
}]
|
||||
end
|
||||
|
||||
include_examples "reports offenses"
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,45 @@
|
||||
module CaskCop
|
||||
shared_examples "does not report any offenses" do
|
||||
it "does not report any offenses" do
|
||||
expect_no_offenses(source)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "reports offenses" do
|
||||
it "reports offenses" do
|
||||
expect_reported_offenses(source, expected_offenses)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "autocorrects source" do
|
||||
it "autocorrects source" do
|
||||
expect_autocorrected_source(source, correct_source)
|
||||
end
|
||||
end
|
||||
|
||||
def expect_no_offenses(source)
|
||||
inspect_source(source)
|
||||
expect(cop.offenses).to be_empty
|
||||
end
|
||||
|
||||
def expect_reported_offenses(source, expected_offenses)
|
||||
inspect_source(source)
|
||||
expect(cop.offenses.size).to eq(expected_offenses.size)
|
||||
expected_offenses.zip(cop.offenses).each do |expected, actual|
|
||||
expect_offense(expected, actual)
|
||||
end
|
||||
end
|
||||
|
||||
def expect_offense(expected, actual)
|
||||
expect(actual.message).to eq(expected[:message])
|
||||
expect(actual.severity).to eq(expected[:severity])
|
||||
expect(actual.line).to eq(expected[:line])
|
||||
expect(actual.column).to eq(expected[:column])
|
||||
expect(actual.location.source).to eq(expected[:source])
|
||||
end
|
||||
|
||||
def expect_autocorrected_source(source, correct_source)
|
||||
new_source = autocorrect_source(source)
|
||||
expect(new_source).to eq(Array(correct_source).join("\n"))
|
||||
end
|
||||
end
|
||||
300
Library/Homebrew/test/rubocops/cask/stanza_grouping_spec.rb
Normal file
300
Library/Homebrew/test/rubocops/cask/stanza_grouping_spec.rb
Normal file
@ -0,0 +1,300 @@
|
||||
require "rubocops/rubocop-cask"
|
||||
require "test/rubocops/cask/shared_examples/cask_cop"
|
||||
|
||||
describe RuboCop::Cop::Cask::StanzaGrouping do
|
||||
include CaskCop
|
||||
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
let(:missing_line_msg) do
|
||||
"stanza groups should be separated by a single empty line"
|
||||
end
|
||||
let(:extra_line_msg) do
|
||||
"stanzas within the same group should have no lines between them"
|
||||
end
|
||||
|
||||
context "when there is only one stanza" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when no stanzas are incorrectly grouped" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when one stanza is incorrectly grouped" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
|
||||
sha256 :no_check
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:expected_offenses) do
|
||||
[{
|
||||
message: extra_line_msg,
|
||||
severity: :convention,
|
||||
line: 3,
|
||||
column: 0,
|
||||
source: "\n",
|
||||
}]
|
||||
end
|
||||
|
||||
include_examples "reports offenses"
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
context "when many stanzas are incorrectly grouped" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
|
||||
name 'Foo'
|
||||
|
||||
homepage 'https://foo.example.com'
|
||||
|
||||
app 'Foo.app'
|
||||
uninstall :quit => 'com.example.foo',
|
||||
:kext => 'com.example.foo.kextextension'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
name 'Foo'
|
||||
homepage 'https://foo.example.com'
|
||||
|
||||
app 'Foo.app'
|
||||
|
||||
uninstall :quit => 'com.example.foo',
|
||||
:kext => 'com.example.foo.kextextension'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:expected_offenses) do
|
||||
[{
|
||||
message: missing_line_msg,
|
||||
severity: :convention,
|
||||
line: 4,
|
||||
column: 0,
|
||||
source: " url 'https://foo.example.com/foo.zip'",
|
||||
}, {
|
||||
message: extra_line_msg,
|
||||
severity: :convention,
|
||||
line: 5,
|
||||
column: 0,
|
||||
source: "\n",
|
||||
}, {
|
||||
message: extra_line_msg,
|
||||
severity: :convention,
|
||||
line: 7,
|
||||
column: 0,
|
||||
source: "\n",
|
||||
}, {
|
||||
message: missing_line_msg,
|
||||
severity: :convention,
|
||||
line: 11,
|
||||
column: 0,
|
||||
source: " uninstall :quit => 'com.example.foo',",
|
||||
}]
|
||||
end
|
||||
|
||||
include_examples "reports offenses"
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
context "when caveats stanza is incorrectly grouped" do
|
||||
let(:source) do
|
||||
format(<<-CASK.undent, caveats: caveats.strip)
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
name 'Foo'
|
||||
app 'Foo.app'
|
||||
%{caveats}
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
format(<<-CASK.undent, caveats: caveats.strip)
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
name 'Foo'
|
||||
|
||||
app 'Foo.app'
|
||||
|
||||
%{caveats}
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
context "when caveats is a one-line string" do
|
||||
let(:caveats) { "caveats 'This is a one-line caveat.'" }
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
context "when caveats is a heredoc" do
|
||||
let(:caveats) do
|
||||
<<-CAVEATS.undent
|
||||
caveats <<-EOS.undent
|
||||
This is a multiline caveat.
|
||||
|
||||
Let's hope it doesn't cause any problems!
|
||||
EOS
|
||||
CAVEATS
|
||||
end
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
context "when caveats is a block" do
|
||||
let(:caveats) do
|
||||
<<-CAVEATS.undent
|
||||
caveats do
|
||||
puts 'This is a multiline caveat.'
|
||||
|
||||
puts "Let's hope it doesn't cause any problems!"
|
||||
end
|
||||
CAVEATS
|
||||
end
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the postflight stanza is incorrectly grouped" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
name 'Foo'
|
||||
app 'Foo.app'
|
||||
postflight do
|
||||
puts 'We have liftoff!'
|
||||
end
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
name 'Foo'
|
||||
|
||||
app 'Foo.app'
|
||||
|
||||
postflight do
|
||||
puts 'We have liftoff!'
|
||||
end
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
context "when a stanza has a comment" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
# comment with an empty line between
|
||||
|
||||
# comment directly above
|
||||
postflight do
|
||||
puts 'We have liftoff!'
|
||||
end
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
name 'Foo'
|
||||
app 'Foo.app'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
|
||||
# comment with an empty line between
|
||||
|
||||
# comment directly above
|
||||
postflight do
|
||||
puts 'We have liftoff!'
|
||||
end
|
||||
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
name 'Foo'
|
||||
|
||||
app 'Foo.app'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
# TODO: detect incorrectly grouped stanzas in nested expressions
|
||||
context "when stanzas are nested in a conditional expression" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
if true
|
||||
version :latest
|
||||
|
||||
sha256 :no_check
|
||||
end
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
end
|
||||
306
Library/Homebrew/test/rubocops/cask/stanza_order_spec.rb
Normal file
306
Library/Homebrew/test/rubocops/cask/stanza_order_spec.rb
Normal file
@ -0,0 +1,306 @@
|
||||
require "rubocops/rubocop-cask"
|
||||
require "test/rubocops/cask/shared_examples/cask_cop"
|
||||
|
||||
describe RuboCop::Cop::Cask::StanzaOrder do
|
||||
include CaskCop
|
||||
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
context "when there is only one stanza" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when no stanzas are out of order" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
|
||||
context "when one pair of stanzas is out of order" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
sha256 :no_check
|
||||
version :latest
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:expected_offenses) do
|
||||
[{
|
||||
message: "`sha256` stanza out of order",
|
||||
severity: :convention,
|
||||
line: 2,
|
||||
column: 2,
|
||||
source: "sha256 :no_check",
|
||||
}, {
|
||||
message: "`version` stanza out of order",
|
||||
severity: :convention,
|
||||
line: 3,
|
||||
column: 2,
|
||||
source: "version :latest",
|
||||
}]
|
||||
end
|
||||
|
||||
include_examples "reports offenses"
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
context "when many stanzas are out of order" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
uninstall :quit => 'com.example.foo',
|
||||
:kext => 'com.example.foo.kext'
|
||||
version :latest
|
||||
app 'Foo.app'
|
||||
sha256 :no_check
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
app 'Foo.app'
|
||||
uninstall :quit => 'com.example.foo',
|
||||
:kext => 'com.example.foo.kext'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:expected_offenses) do
|
||||
[{
|
||||
message: "`url` stanza out of order",
|
||||
severity: :convention,
|
||||
line: 2,
|
||||
column: 2,
|
||||
source: "url 'https://foo.example.com/foo.zip'",
|
||||
}, {
|
||||
message: "`uninstall` stanza out of order",
|
||||
severity: :convention,
|
||||
line: 3,
|
||||
column: 2,
|
||||
source: "uninstall :quit => 'com.example.foo',\n" \
|
||||
" :kext => 'com.example.foo.kext'",
|
||||
}, {
|
||||
message: "`version` stanza out of order",
|
||||
severity: :convention,
|
||||
line: 5,
|
||||
column: 2,
|
||||
source: "version :latest",
|
||||
}, {
|
||||
message: "`sha256` stanza out of order",
|
||||
severity: :convention,
|
||||
line: 7,
|
||||
column: 2,
|
||||
source: "sha256 :no_check",
|
||||
}]
|
||||
end
|
||||
|
||||
include_examples "reports offenses"
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
context "when a stanza appears multiple times" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
name 'Foo'
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
name 'FancyFoo'
|
||||
version :latest
|
||||
app 'Foo.app'
|
||||
sha256 :no_check
|
||||
name 'FunkyFoo'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
name 'Foo'
|
||||
name 'FancyFoo'
|
||||
name 'FunkyFoo'
|
||||
app 'Foo.app'
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
it "preserves the original order" do
|
||||
expect_autocorrected_source(source, correct_source)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a stanza has a comment" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
# comment with an empty line between
|
||||
|
||||
# comment directly above
|
||||
postflight do
|
||||
puts 'We have liftoff!'
|
||||
end
|
||||
sha256 :no_check # comment on same line
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check # comment on same line
|
||||
# comment with an empty line between
|
||||
|
||||
# comment directly above
|
||||
postflight do
|
||||
puts 'We have liftoff!'
|
||||
end
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
context "when the caveats stanza is out of order" do
|
||||
let(:source) do
|
||||
format(<<-CASK.undent, caveats: caveats.strip)
|
||||
cask 'foo' do
|
||||
name 'Foo'
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
%{caveats}
|
||||
version :latest
|
||||
app 'Foo.app'
|
||||
sha256 :no_check
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
format(<<-CASK.undent, caveats: caveats.strip)
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
name 'Foo'
|
||||
app 'Foo.app'
|
||||
%{caveats}
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
context "when caveats is a one-line string" do
|
||||
let(:caveats) { "caveats 'This is a one-line caveat.'" }
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
context "when caveats is a heredoc" do
|
||||
let(:caveats) do
|
||||
<<-CAVEATS.undent
|
||||
caveats <<-EOS.undent
|
||||
This is a multiline caveat.
|
||||
|
||||
Let's hope it doesn't cause any problems!
|
||||
EOS
|
||||
CAVEATS
|
||||
end
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
context "when caveats is a block" do
|
||||
let(:caveats) do
|
||||
<<-CAVEATS.undent
|
||||
caveats do
|
||||
puts 'This is a multiline caveat.'
|
||||
|
||||
puts "Let's hope it doesn't cause any problems!"
|
||||
end
|
||||
CAVEATS
|
||||
end
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the postflight stanza is out of order" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
name 'Foo'
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
postflight do
|
||||
puts 'We have liftoff!'
|
||||
end
|
||||
version :latest
|
||||
app 'Foo.app'
|
||||
sha256 :no_check
|
||||
end
|
||||
CASK
|
||||
end
|
||||
let(:correct_source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url 'https://foo.example.com/foo.zip'
|
||||
name 'Foo'
|
||||
app 'Foo.app'
|
||||
postflight do
|
||||
puts 'We have liftoff!'
|
||||
end
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "autocorrects source"
|
||||
end
|
||||
|
||||
# TODO: detect out-of-order stanzas in nested expressions
|
||||
context "when stanzas are nested in a conditional expression" do
|
||||
let(:source) do
|
||||
<<-CASK.undent
|
||||
cask 'foo' do
|
||||
if true
|
||||
sha256 :no_check
|
||||
version :latest
|
||||
end
|
||||
end
|
||||
CASK
|
||||
end
|
||||
|
||||
include_examples "does not report any offenses"
|
||||
end
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user