2019-04-19 15:38:03 +09:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-10-26 19:41:14 +01:00
|
|
|
require "rubocops/extend/formula"
|
2017-04-08 15:10:44 +05:30
|
|
|
|
|
|
|
module RuboCop
|
|
|
|
module Cop
|
2018-04-24 18:27:37 +10:00
|
|
|
module FormulaAudit
|
2018-10-18 21:42:43 -04:00
|
|
|
# This cop checks for correct order of components in Formulae.
|
2017-04-08 15:10:44 +05:30
|
|
|
#
|
2018-10-18 21:42:43 -04:00
|
|
|
# - `component_precedence_list` has component hierarchy in a nested list
|
2017-04-08 15:10:44 +05:30
|
|
|
# where each sub array contains components' details which are at same precedence level
|
2017-05-03 11:33:00 +05:30
|
|
|
class ComponentsOrder < FormulaCop
|
2020-04-23 11:32:23 +02:00
|
|
|
# `aspell`: options and resources should be grouped by language
|
2020-06-06 19:12:12 +01:00
|
|
|
COMPONENT_ALLOWLIST = %w[
|
2020-04-23 11:32:23 +02:00
|
|
|
aspell
|
|
|
|
].freeze
|
|
|
|
|
2017-06-19 00:36:18 -04:00
|
|
|
def audit_formula(_node, _class_node, _parent_class_node, body_node)
|
2017-04-08 15:10:44 +05:30
|
|
|
component_precedence_list = [
|
2020-05-15 18:28:47 +05:30
|
|
|
[{ name: :include, type: :method_call }],
|
|
|
|
[{ name: :desc, type: :method_call }],
|
|
|
|
[{ name: :homepage, type: :method_call }],
|
|
|
|
[{ name: :url, type: :method_call }],
|
|
|
|
[{ name: :mirror, type: :method_call }],
|
|
|
|
[{ name: :version, type: :method_call }],
|
|
|
|
[{ name: :sha256, type: :method_call }],
|
|
|
|
[{ name: :revision, type: :method_call }],
|
2017-04-08 15:10:44 +05:30
|
|
|
[{ name: :version_scheme, type: :method_call }],
|
2020-05-15 18:28:47 +05:30
|
|
|
[{ name: :head, type: :method_call }],
|
|
|
|
[{ name: :stable, type: :block_call }],
|
|
|
|
[{ name: :livecheck, type: :block_call }],
|
|
|
|
[{ name: :bottle, type: :block_call }],
|
2018-09-10 08:01:12 +10:00
|
|
|
[{ name: :pour_bottle?, type: :block_call }],
|
2020-05-15 18:28:47 +05:30
|
|
|
[{ name: :devel, type: :block_call }],
|
|
|
|
[{ name: :head, type: :block_call }],
|
|
|
|
[{ name: :bottle, type: :method_call }],
|
|
|
|
[{ name: :keg_only, type: :method_call }],
|
|
|
|
[{ name: :option, type: :method_call }],
|
2018-09-09 10:51:32 +10:00
|
|
|
[{ name: :deprecated_option, type: :method_call }],
|
2017-04-08 15:10:44 +05:30
|
|
|
[{ name: :depends_on, type: :method_call }],
|
2020-01-12 10:59:22 -08:00
|
|
|
[{ name: :uses_from_macos, type: :method_call }],
|
2020-04-23 11:32:23 +02:00
|
|
|
[{ name: :on_macos, type: :block_call }],
|
|
|
|
[{ name: :on_linux, type: :block_call }],
|
2017-04-08 15:10:44 +05:30
|
|
|
[{ name: :conflicts_with, type: :method_call }],
|
2018-09-09 10:51:32 +10:00
|
|
|
[{ name: :skip_clean, type: :method_call }],
|
2018-09-10 08:01:12 +10:00
|
|
|
[{ name: :cxxstdlib_check, type: :method_call }],
|
2018-09-11 22:05:45 +10:00
|
|
|
[{ name: :link_overwrite, type: :method_call }],
|
2018-09-09 10:51:32 +10:00
|
|
|
[{ name: :fails_with, type: :method_call }, { name: :fails_with, type: :block_call }],
|
2017-04-08 15:10:44 +05:30
|
|
|
[{ name: :go_resource, type: :block_call }, { name: :resource, type: :block_call }],
|
2018-09-09 10:51:32 +10:00
|
|
|
[{ name: :patch, type: :method_call }, { name: :patch, type: :block_call }],
|
|
|
|
[{ name: :needs, type: :method_call }],
|
2017-04-08 15:10:44 +05:30
|
|
|
[{ name: :install, type: :method_definition }],
|
2018-04-27 10:26:58 +10:00
|
|
|
[{ name: :post_install, type: :method_definition }],
|
2017-04-08 15:10:44 +05:30
|
|
|
[{ name: :caveats, type: :method_definition }],
|
|
|
|
[{ name: :plist_options, type: :method_call }, { name: :plist, type: :method_definition }],
|
|
|
|
[{ name: :test, type: :block_call }],
|
|
|
|
]
|
|
|
|
|
2020-04-23 11:32:23 +02:00
|
|
|
@present_components, @offensive_nodes = check_order(component_precedence_list, body_node)
|
|
|
|
|
|
|
|
component_problem @offensive_nodes[0], @offensive_nodes[1] if @offensive_nodes
|
|
|
|
|
|
|
|
component_precedence_list = [
|
|
|
|
[{ name: :depends_on, type: :method_call }],
|
|
|
|
[{ name: :resource, type: :block_call }],
|
|
|
|
[{ name: :patch, type: :method_call }, { name: :patch, type: :block_call }],
|
|
|
|
]
|
|
|
|
|
|
|
|
on_macos_blocks = find_blocks(body_node, :on_macos)
|
|
|
|
|
|
|
|
if on_macos_blocks.length > 1
|
|
|
|
@offensive_node = on_macos_blocks.second
|
|
|
|
@offense_source_range = on_macos_blocks.second.source_range
|
|
|
|
problem "there can only be one `on_macos` block in a formula."
|
2017-04-08 15:10:44 +05:30
|
|
|
end
|
|
|
|
|
2020-04-23 11:32:23 +02:00
|
|
|
check_on_os_block_content(component_precedence_list, on_macos_blocks.first) if on_macos_blocks.any?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2020-04-23 11:32:23 +02:00
|
|
|
on_linux_blocks = find_blocks(body_node, :on_linux)
|
|
|
|
|
|
|
|
if on_linux_blocks.length > 1
|
|
|
|
@offensive_node = on_linux_blocks.second
|
|
|
|
@offense_source_range = on_linux_blocks.second.source_range
|
|
|
|
problem "there can only be one `on_linux` block in a formula."
|
|
|
|
end
|
|
|
|
|
|
|
|
check_on_os_block_content(component_precedence_list, on_linux_blocks.first) if on_linux_blocks.any?
|
|
|
|
|
|
|
|
resource_blocks = find_blocks(body_node, :resource)
|
|
|
|
resource_blocks.each do |resource_block|
|
|
|
|
on_macos_blocks = find_blocks(resource_block.body, :on_macos)
|
|
|
|
on_linux_blocks = find_blocks(resource_block.body, :on_linux)
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2020-04-23 11:32:23 +02:00
|
|
|
if on_macos_blocks.length.zero? && on_linux_blocks.length.zero?
|
|
|
|
# Found nothing. Try without .body as depending on the code,
|
|
|
|
# on_macos or on_linux might be in .body or not ...
|
|
|
|
on_macos_blocks = find_blocks(resource_block, :on_macos)
|
|
|
|
on_linux_blocks = find_blocks(resource_block, :on_linux)
|
|
|
|
|
|
|
|
next if on_macos_blocks.length.zero? && on_linux_blocks.length.zero?
|
|
|
|
end
|
|
|
|
|
|
|
|
@offensive_node = resource_block
|
|
|
|
@offense_source_range = resource_block.source_range
|
|
|
|
|
|
|
|
if on_macos_blocks.length > 1
|
|
|
|
problem "there can only be one `on_macos` block in a resource block."
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
if on_linux_blocks.length > 1
|
|
|
|
problem "there can only be one `on_linux` block in a resource block."
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
if on_macos_blocks.length == 1 && on_linux_blocks.length.zero?
|
|
|
|
problem "you need to define an `on_linux` block within your resource block."
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
if on_macos_blocks.length.zero? && on_linux_blocks.length == 1
|
|
|
|
problem "you need to define an `on_macos` block within your resource block."
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
on_macos_block = on_macos_blocks.first
|
|
|
|
on_linux_block = on_linux_blocks.first
|
|
|
|
|
|
|
|
child_nodes = on_macos_block.body.child_nodes
|
|
|
|
if child_nodes[0].method_name.to_s != "url" && child_nodes[1].method_name.to_s != "sha256"
|
|
|
|
problem "only an url and a sha256 (in the right order) are allowed in a `on_macos` " \
|
|
|
|
"block within a resource block."
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
child_nodes = on_linux_block.body.child_nodes
|
|
|
|
if child_nodes[0].method_name.to_s != "url" && child_nodes[1].method_name.to_s != "sha256"
|
|
|
|
problem "only an url and a sha256 (in the right order) are allowed in a `on_linux` " \
|
|
|
|
"block within a resource block."
|
2017-05-08 18:47:50 +05:30
|
|
|
end
|
2017-04-08 15:10:44 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-04-23 11:32:23 +02:00
|
|
|
def check_on_os_block_content(component_precedence_list, on_os_block)
|
|
|
|
_, offensive_node = check_order(component_precedence_list, on_os_block.body)
|
|
|
|
component_problem(*offensive_node) if offensive_node
|
|
|
|
on_os_block.body.child_nodes.each do |child|
|
|
|
|
valid_node = depends_on_node?(child)
|
|
|
|
# Check for RuboCop::AST::SendNode instances only, as we are checking the
|
|
|
|
# method_name for patches and resources.
|
|
|
|
next unless child.instance_of? RuboCop::AST::SendNode
|
2018-04-24 18:27:37 +10:00
|
|
|
|
2020-04-23 11:32:23 +02:00
|
|
|
valid_node ||= child.method_name.to_s == "patch"
|
|
|
|
valid_node ||= child.method_name.to_s == "resource"
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2020-04-23 11:32:23 +02:00
|
|
|
@offensive_node = on_os_block
|
|
|
|
@offense_source_range = on_os_block.source_range
|
|
|
|
unless valid_node
|
|
|
|
problem "`#{on_os_block.method_name}` can only include `depends_on`, `patch` and `resource` nodes."
|
|
|
|
end
|
|
|
|
end
|
2017-04-08 15:10:44 +05:30
|
|
|
end
|
2017-05-08 18:47:50 +05:30
|
|
|
|
|
|
|
# autocorrect method gets called just after component_problem method call
|
|
|
|
def autocorrect(_node)
|
2020-04-23 11:32:23 +02:00
|
|
|
return if @offensive_nodes.nil?
|
|
|
|
|
2017-05-08 18:47:50 +05:30
|
|
|
succeeding_node = @offensive_nodes[0]
|
|
|
|
preceding_node = @offensive_nodes[1]
|
|
|
|
lambda do |corrector|
|
|
|
|
reorder_components(corrector, succeeding_node, preceding_node)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-10-18 21:42:43 -04:00
|
|
|
# Reorder two nodes in the source, using the corrector instance in autocorrect method.
|
|
|
|
# Components of same type are grouped together when rewriting the source.
|
|
|
|
# Linebreaks are introduced if components are of two different methods/blocks/multilines.
|
2017-05-08 18:47:50 +05:30
|
|
|
def reorder_components(corrector, node1, node2)
|
|
|
|
# order_idx : node1's index in component_precedence_list
|
|
|
|
# curr_p_idx: node1's index in preceding_comp_arr
|
|
|
|
# preceding_comp_arr: array containing components of same type
|
|
|
|
order_idx, curr_p_idx, preceding_comp_arr = get_state(node1)
|
|
|
|
|
2017-09-24 20:12:58 +01:00
|
|
|
# curr_p_idx.positive? means node1 needs to be grouped with its own kind
|
|
|
|
if curr_p_idx.positive?
|
2017-06-01 16:06:51 +02:00
|
|
|
node2 = preceding_comp_arr[curr_p_idx - 1]
|
2017-05-08 18:47:50 +05:30
|
|
|
indentation = " " * (start_column(node2) - line_start_column(node2))
|
|
|
|
line_breaks = node2.multiline? ? "\n\n" : "\n"
|
2017-06-01 16:06:51 +02:00
|
|
|
corrector.insert_after(node2.source_range, line_breaks + indentation + node1.source)
|
2017-05-08 18:47:50 +05:30
|
|
|
else
|
|
|
|
indentation = " " * (start_column(node2) - line_start_column(node2))
|
2019-08-19 14:27:29 +10:00
|
|
|
# No line breaks up to version_scheme, order_idx == 8
|
2017-06-01 16:06:51 +02:00
|
|
|
line_breaks = (order_idx > 8) ? "\n\n" : "\n"
|
|
|
|
corrector.insert_before(node2.source_range, node1.source + line_breaks + indentation)
|
2017-05-08 18:47:50 +05:30
|
|
|
end
|
2018-01-07 15:40:42 +00:00
|
|
|
corrector.remove(range_with_surrounding_space(range: node1.source_range, side: :left))
|
2017-05-08 18:47:50 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
# Returns precedence index and component's index to properly reorder and group during autocorrect
|
|
|
|
def get_state(node1)
|
|
|
|
@present_components.each_with_index do |comp, idx|
|
|
|
|
return [idx, comp.index(node1), comp] if comp.member?(node1)
|
|
|
|
end
|
|
|
|
end
|
2020-04-23 11:32:23 +02:00
|
|
|
|
|
|
|
def check_order(component_precedence_list, body_node)
|
|
|
|
present_components = component_precedence_list.map do |components|
|
|
|
|
components.flat_map do |component|
|
|
|
|
case component[:type]
|
|
|
|
when :method_call
|
|
|
|
find_method_calls_by_name(body_node, component[:name]).to_a
|
|
|
|
when :block_call
|
|
|
|
find_blocks(body_node, component[:name]).to_a
|
|
|
|
when :method_definition
|
|
|
|
find_method_def(body_node, component[:name])
|
|
|
|
end
|
|
|
|
end.compact
|
|
|
|
end
|
|
|
|
|
|
|
|
# Check if each present_components is above rest of the present_components
|
|
|
|
offensive_nodes = nil
|
|
|
|
present_components.take(present_components.size - 1).each_with_index do |preceding_component, p_idx|
|
|
|
|
next if preceding_component.empty?
|
|
|
|
|
|
|
|
present_components.drop(p_idx + 1).each do |succeeding_component|
|
|
|
|
next if succeeding_component.empty?
|
|
|
|
|
|
|
|
offensive_nodes = check_precedence(preceding_component, succeeding_component)
|
2020-06-30 08:36:11 +01:00
|
|
|
return [present_components, offensive_nodes] if offensive_nodes
|
2020-04-23 11:32:23 +02:00
|
|
|
end
|
|
|
|
end
|
2020-06-30 08:36:11 +01:00
|
|
|
nil
|
2020-04-23 11:32:23 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
# Method to format message for reporting component precedence violations
|
|
|
|
def component_problem(c1, c2)
|
2020-06-06 19:12:12 +01:00
|
|
|
return if COMPONENT_ALLOWLIST.include?(@formula_name)
|
2020-04-23 11:32:23 +02:00
|
|
|
|
|
|
|
problem "`#{format_component(c1)}` (line #{line_number(c1)}) " \
|
|
|
|
"should be put before `#{format_component(c2)}` " \
|
|
|
|
"(line #{line_number(c2)})"
|
|
|
|
end
|
|
|
|
|
|
|
|
# Node pattern method to match
|
|
|
|
# `depends_on` variants
|
|
|
|
def_node_matcher :depends_on_node?, <<~EOS
|
|
|
|
{(if _ (send nil? :depends_on ...) nil?)
|
|
|
|
(send nil? :depends_on ...)}
|
|
|
|
EOS
|
2017-04-08 15:10:44 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|