| 
									
										
										
										
											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 }], | 
					
						
							| 
									
										
										
										
											2020-06-30 23:25:51 +08:00
										 |  |  |             [{ name: :license, 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: :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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 23:08:43 +02:00
										 |  |  |             next if on_macos_blocks.length.zero? && on_linux_blocks.length.zero? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if on_macos_blocks.length == 1
 | 
					
						
							|  |  |  |               on_macos_block = on_macos_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 | 
					
						
							| 
									
										
										
										
											2020-04-23 11:32:23 +02:00
										 |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 23:08:43 +02:00
										 |  |  |             if on_linux_blocks.length == 1
 | 
					
						
							|  |  |  |               on_linux_block = on_linux_blocks.first | 
					
						
							|  |  |  |               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." | 
					
						
							|  |  |  |               end | 
					
						
							| 
									
										
										
										
											2020-04-23 11:32:23 +02:00
										 |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 23:08:43 +02:00
										 |  |  |             if on_macos_blocks.length > 1
 | 
					
						
							|  |  |  |               problem "there can only be one `on_macos` block in a resource block." | 
					
						
							| 
									
										
										
										
											2020-04-23 11:32:23 +02:00
										 |  |  |               next | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 23:08:43 +02:00
										 |  |  |             if on_linux_blocks.length > 1
 | 
					
						
							|  |  |  |               problem "there can only be one `on_linux` block in a resource block." | 
					
						
							| 
									
										
										
										
											2020-04-23 11:32:23 +02:00
										 |  |  |               next | 
					
						
							|  |  |  |             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 |