| 
									
										
										
										
											2023-02-14 19:19:37 -08:00
										 |  |  | # typed: true | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-20 10:22:39 -08:00
										 |  |  | require "rubocops/extend/formula_cop" | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  | module RuboCop | 
					
						
							|  |  |  |   module Cop | 
					
						
							| 
									
										
										
										
											2018-09-08 15:46:56 +10:00
										 |  |  |     module FormulaAudit | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # This cop checks for correct order of `depends_on` in formulae. | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |       # | 
					
						
							|  |  |  |       # precedence order: | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # build-time > test > normal > recommended > optional | 
					
						
							| 
									
										
										
										
											2023-02-20 18:10:59 -08:00
										 |  |  |       class DependencyOrder < FormulaCop | 
					
						
							| 
									
										
										
										
											2021-01-12 14:52:47 +11:00
										 |  |  |         extend AutoCorrector | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |         def audit_formula(_node, _class_node, _parent_class_node, body_node) | 
					
						
							|  |  |  |           check_dependency_nodes_order(body_node) | 
					
						
							| 
									
										
										
										
											2020-01-12 10:59:22 -08:00
										 |  |  |           check_uses_from_macos_nodes_order(body_node) | 
					
						
							| 
									
										
										
										
											2022-08-02 21:56:45 -07:00
										 |  |  |           ([:head, :stable] + on_system_methods).each do |block_name| | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |             block = find_block(body_node, block_name) | 
					
						
							|  |  |  |             next unless block | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |             check_dependency_nodes_order(block.body) | 
					
						
							| 
									
										
										
										
											2020-01-12 10:59:22 -08:00
										 |  |  |             check_uses_from_macos_nodes_order(block.body) | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-12 10:59:22 -08:00
										 |  |  |         def check_uses_from_macos_nodes_order(parent_node) | 
					
						
							|  |  |  |           return if parent_node.nil? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           dependency_nodes = parent_node.each_child_node.select { |x| uses_from_macos_node?(x) } | 
					
						
							|  |  |  |           ensure_dependency_order(dependency_nodes) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |         def check_dependency_nodes_order(parent_node) | 
					
						
							|  |  |  |           return if parent_node.nil? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-12 10:59:22 -08:00
										 |  |  |           dependency_nodes = parent_node.each_child_node.select { |x| depends_on_node?(x) } | 
					
						
							|  |  |  |           ensure_dependency_order(dependency_nodes) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def ensure_dependency_order(nodes) | 
					
						
							|  |  |  |           ordered = nodes.sort_by { |node| dependency_name(node).downcase } | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |           ordered = sort_dependencies_by_type(ordered) | 
					
						
							|  |  |  |           sort_conditional_dependencies!(ordered) | 
					
						
							|  |  |  |           verify_order_in_source(ordered) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Separate dependencies according to precedence order: | 
					
						
							| 
									
										
										
										
											2018-03-28 20:54:39 -05:00
										 |  |  |         # build-time > test > normal > recommended > optional | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |         def sort_dependencies_by_type(dependency_nodes) | 
					
						
							| 
									
										
										
										
											2018-03-28 20:54:39 -05:00
										 |  |  |           unsorted_deps = dependency_nodes.to_a | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |           ordered = [] | 
					
						
							| 
									
										
										
										
											2018-03-28 20:54:39 -05:00
										 |  |  |           ordered.concat(unsorted_deps.select { |dep| buildtime_dependency? dep }) | 
					
						
							|  |  |  |           unsorted_deps -= ordered | 
					
						
							|  |  |  |           ordered.concat(unsorted_deps.select { |dep| test_dependency? dep }) | 
					
						
							|  |  |  |           unsorted_deps -= ordered | 
					
						
							|  |  |  |           ordered.concat(unsorted_deps.reject { |dep| negate_normal_dependency? dep }) | 
					
						
							|  |  |  |           unsorted_deps -= ordered | 
					
						
							|  |  |  |           ordered.concat(unsorted_deps.select { |dep| recommended_dependency? dep }) | 
					
						
							|  |  |  |           unsorted_deps -= ordered | 
					
						
							|  |  |  |           ordered.concat(unsorted_deps.select { |dep| optional_dependency? dep }) | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # `depends_on :apple if build.with? "foo"` should always be defined | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |         #  after `depends_on :foo`. | 
					
						
							|  |  |  |         # This method reorders the dependencies array according to the above rule. | 
					
						
							| 
									
										
										
										
											2023-02-14 19:19:37 -08:00
										 |  |  |         sig { params(ordered: T::Array[RuboCop::AST::Node]).returns(T::Array[RuboCop::AST::Node]) } | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |         def sort_conditional_dependencies!(ordered) | 
					
						
							|  |  |  |           length = ordered.size | 
					
						
							|  |  |  |           idx = 0
 | 
					
						
							|  |  |  |           while idx < length | 
					
						
							| 
									
										
										
										
											2023-02-14 19:19:37 -08:00
										 |  |  |             idx1 = T.let(nil, T.nilable(Integer)) | 
					
						
							|  |  |  |             idx2 = T.let(nil, T.nilable(Integer)) | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |             ordered.each_with_index do |dep, pos| | 
					
						
							|  |  |  |               idx = pos+1
 | 
					
						
							|  |  |  |               match_nodes = build_with_dependency_name(dep) | 
					
						
							| 
									
										
										
										
											2024-01-26 13:47:59 -08:00
										 |  |  |               next if match_nodes.blank? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-30 15:17:15 +05:30
										 |  |  |               idx1 = pos | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |               ordered.drop(idx1+1).each_with_index do |dep2, pos2| | 
					
						
							|  |  |  |                 next unless match_nodes.index(dependency_name(dep2)) | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |                 idx2 = pos2 if idx2.nil? || pos2 > idx2 | 
					
						
							|  |  |  |               end | 
					
						
							|  |  |  |               break if idx2 | 
					
						
							|  |  |  |             end | 
					
						
							| 
									
										
										
										
											2023-02-14 19:19:37 -08:00
										 |  |  |             insert_after!(ordered, idx1, idx2 + T.must(idx1)) if idx2 | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |           end | 
					
						
							|  |  |  |           ordered | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |         # Verify actual order of sorted `depends_on` nodes in source code; | 
					
						
							|  |  |  |         # raise RuboCop problem otherwise. | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |         def verify_order_in_source(ordered) | 
					
						
							| 
									
										
										
										
											2021-01-12 14:52:47 +11:00
										 |  |  |           ordered.each_with_index do |node_1, idx| | 
					
						
							|  |  |  |             l1 = line_number(node_1) | 
					
						
							| 
									
										
										
										
											2023-02-14 19:19:37 -08:00
										 |  |  |             l2 = T.let(nil, T.nilable(Integer)) | 
					
						
							|  |  |  |             node_2 = T.let(nil, T.nilable(RuboCop::AST::Node)) | 
					
						
							| 
									
										
										
										
											2021-01-12 14:52:47 +11:00
										 |  |  |             ordered.drop(idx + 1).each do |test_node| | 
					
						
							|  |  |  |               l2 = line_number(test_node) | 
					
						
							|  |  |  |               node_2 = test_node if l2 < l1 | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |             end | 
					
						
							| 
									
										
										
										
											2021-01-12 14:52:47 +11:00
										 |  |  |             next unless node_2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             offending_node(node_1) | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 10:09:59 +01:00
										 |  |  |             problem "dependency \"#{dependency_name(node_1)}\" (line #{l1}) should be put before dependency " \ | 
					
						
							| 
									
										
										
										
											2021-01-12 14:52:47 +11:00
										 |  |  |                     "\"#{dependency_name(node_2)}\" (line #{l2})" do |corrector| | 
					
						
							|  |  |  |               indentation = " " * (start_column(node_2) - line_start_column(node_2)) | 
					
						
							|  |  |  |               line_breaks = "\n" | 
					
						
							|  |  |  |               corrector.insert_before(node_2.source_range, | 
					
						
							|  |  |  |                                       node_1.source + line_breaks + indentation) | 
					
						
							|  |  |  |               corrector.remove(range_with_surrounding_space(range: node_1.source_range, side: :left)) | 
					
						
							|  |  |  |             end | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Node pattern method to match | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |         # `depends_on` variants. | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |         def_node_matcher :depends_on_node?, <<~EOS | 
					
						
							|  |  |  |           {(if _ (send nil? :depends_on ...) nil?) | 
					
						
							|  |  |  |            (send nil? :depends_on ...)} | 
					
						
							|  |  |  |         EOS | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-12 10:59:22 -08:00
										 |  |  |         def_node_matcher :uses_from_macos_node?, <<~EOS | 
					
						
							|  |  |  |           {(if _ (send nil? :uses_from_macos ...) nil?) | 
					
						
							|  |  |  |            (send nil? :uses_from_macos ...)} | 
					
						
							|  |  |  |         EOS | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |         def_node_search :buildtime_dependency?, "(sym :build)" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def_node_search :recommended_dependency?, "(sym :recommended)" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-19 10:11:08 +00:00
										 |  |  |         def_node_search :test_dependency?, "(sym :test)" | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  |         def_node_search :optional_dependency?, "(sym :optional)" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-19 10:11:08 +00:00
										 |  |  |         def_node_search :negate_normal_dependency?, "(sym {:build :recommended :test :optional})" | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-12 10:59:22 -08:00
										 |  |  |         # Node pattern method to extract `name` in `depends_on :name` or `uses_from_macos :name` | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |         def_node_search :dependency_name_node, <<~EOS | 
					
						
							| 
									
										
										
										
											2020-04-28 14:17:54 +01:00
										 |  |  |           {(send nil? {:depends_on :uses_from_macos} {(hash (pair $_ _) ...) $({str sym} _) $(const nil? _)} ...) | 
					
						
							| 
									
										
										
										
											2018-06-11 04:14:13 +02:00
										 |  |  |            (if _ (send nil? :depends_on {(hash (pair $_ _)) $({str sym} _) $(const nil? _)}) nil?)} | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |         EOS | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Node pattern method to extract `name` in `build.with? :name` | 
					
						
							|  |  |  |         def_node_search :build_with_dependency_node, <<~EOS | 
					
						
							|  |  |  |           (send (send nil? :build) :with? $({str sym} _)) | 
					
						
							|  |  |  |         EOS | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def insert_after!(arr, idx1, idx2) | 
					
						
							|  |  |  |           arr.insert(idx2+1, arr.delete_at(idx1)) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def build_with_dependency_name(node) | 
					
						
							|  |  |  |           match_nodes = build_with_dependency_node(node) | 
					
						
							| 
									
										
										
										
											2023-12-14 02:52:30 +00:00
										 |  |  |           match_nodes = match_nodes.to_a.compact | 
					
						
							| 
									
										
										
										
											2018-02-19 23:40:07 +05:30
										 |  |  |           match_nodes.map { |n| string_content(n) } unless match_nodes.empty? | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def dependency_name(dependency_node) | 
					
						
							|  |  |  |           match_node = dependency_name_node(dependency_node).to_a.first | 
					
						
							|  |  |  |           string_content(match_node) if match_node | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |