| 
									
										
										
										
											2020-10-10 14:16:11 +02:00
										 |  |  | # typed: false | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-26 16:24:48 +02:00
										 |  |  | # Silence compatibility warning. | 
					
						
							|  |  |  | begin | 
					
						
							|  |  |  |   old_verbosity = $VERBOSE | 
					
						
							|  |  |  |   $VERBOSE = nil | 
					
						
							|  |  |  |   require "parser/current" | 
					
						
							|  |  |  | ensure | 
					
						
							|  |  |  |   $VERBOSE = old_verbosity | 
					
						
							|  |  |  | end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-02 23:30:07 +02:00
										 |  |  | require "extend/string" | 
					
						
							| 
									
										
										
										
											2020-07-30 16:29:12 +01:00
										 |  |  | require "rubocops/shared/helper_functions" | 
					
						
							| 
									
										
										
										
											2017-05-30 01:22:47 +05:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-02 20:26:29 +05:30
										 |  |  | module RuboCop | 
					
						
							|  |  |  |   module Cop | 
					
						
							| 
									
										
										
										
											2020-08-26 02:50:44 +02:00
										 |  |  |     # Superclass for all formula cops. | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # @api private | 
					
						
							| 
									
										
										
										
											2021-01-12 02:19:31 +11:00
										 |  |  |     class FormulaCop < Base | 
					
						
							| 
									
										
										
										
											2018-03-07 16:14:55 +00:00
										 |  |  |       include RangeHelp | 
					
						
							| 
									
										
										
										
											2020-07-30 16:29:12 +01:00
										 |  |  |       include HelperFunctions | 
					
						
							| 
									
										
										
										
											2018-03-07 16:14:55 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-04 01:18:00 +05:30
										 |  |  |       attr_accessor :file_path | 
					
						
							| 
									
										
										
										
											2020-05-12 08:32:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-23 04:09:13 +05:30
										 |  |  |       @registry = Cop.registry | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # This method is called by RuboCop and is the main entry point. | 
					
						
							| 
									
										
										
										
											2017-04-23 04:09:13 +05:30
										 |  |  |       def on_class(node) | 
					
						
							| 
									
										
										
										
											2017-08-04 01:18:00 +05:30
										 |  |  |         @file_path = processed_source.buffer.name | 
					
						
							|  |  |  |         return unless file_path_allowed? | 
					
						
							| 
									
										
										
										
											2017-05-24 00:07:06 +05:30
										 |  |  |         return unless formula_class?(node) | 
					
						
							| 
									
										
										
										
											2017-04-23 04:09:13 +05:30
										 |  |  |         return unless respond_to?(:audit_formula) | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-30 01:22:47 +05:30
										 |  |  |         class_node, parent_class_node, @body = *node | 
					
						
							| 
									
										
										
										
											2017-08-30 15:48:41 +05:30
										 |  |  |         @formula_name = Pathname.new(@file_path).basename(".rb").to_s | 
					
						
							| 
									
										
										
										
											2020-11-27 01:23:07 -05:00
										 |  |  |         @tap_style_exceptions = nil | 
					
						
							| 
									
										
										
										
											2017-05-30 01:22:47 +05:30
										 |  |  |         audit_formula(node, class_node, parent_class_node, @body) | 
					
						
							| 
									
										
										
										
											2017-04-23 04:09:13 +05:30
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2017-03-02 20:26:29 +05:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-18 21:42:43 -04:00
										 |  |  |       # Yields to block when there is a match. | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # | 
					
						
							| 
									
										
										
										
											2018-10-18 21:42:43 -04:00
										 |  |  |       # @param urls [Array] url/mirror method call nodes | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # @param regex [Regexp] pattern to match URLs | 
					
						
							| 
									
										
										
										
											2017-07-30 12:57:57 +05:30
										 |  |  |       def audit_urls(urls, regex) | 
					
						
							|  |  |  |         urls.each do |url_node| | 
					
						
							|  |  |  |           url_string_node = parameters(url_node).first | 
					
						
							|  |  |  |           url_string = string_content(url_string_node) | 
					
						
							|  |  |  |           match_object = regex_match_group(url_string_node, regex) | 
					
						
							|  |  |  |           next unless match_object | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-30 12:57:57 +05:30
										 |  |  |           offending_node(url_string_node.parent) | 
					
						
							|  |  |  |           yield match_object, url_string | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-18 21:42:43 -04:00
										 |  |  |       # Returns nil if does not depend on dependency_name. | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # | 
					
						
							| 
									
										
										
										
											2018-10-18 21:42:43 -04:00
										 |  |  |       # @param dependency_name dependency's name | 
					
						
							| 
									
										
										
										
											2017-05-24 21:07:50 +05:30
										 |  |  |       def depends_on?(dependency_name, *types) | 
					
						
							| 
									
										
										
										
											2018-01-07 14:08:58 +00:00
										 |  |  |         types = [:any] if types.empty? | 
					
						
							| 
									
										
										
										
											2017-05-30 01:22:47 +05:30
										 |  |  |         dependency_nodes = find_every_method_call_by_name(@body, :depends_on) | 
					
						
							| 
									
										
										
										
											2017-05-24 00:07:06 +05:30
										 |  |  |         idx = dependency_nodes.index do |n| | 
					
						
							| 
									
										
										
										
											2017-05-24 21:07:50 +05:30
										 |  |  |           types.any? { |type| depends_on_name_type?(n, dependency_name, type) } | 
					
						
							| 
									
										
										
										
											2017-05-24 00:07:06 +05:30
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2017-05-30 01:22:47 +05:30
										 |  |  |         return if idx.nil? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-24 00:07:06 +05:30
										 |  |  |         @offensive_node = dependency_nodes[idx] | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-18 21:42:43 -04:00
										 |  |  |       # Returns true if given dependency name and dependency type exist in given dependency method call node. | 
					
						
							| 
									
										
										
										
											2017-05-30 01:22:47 +05:30
										 |  |  |       # TODO: Add case where key of hash is an array | 
					
						
							|  |  |  |       def depends_on_name_type?(node, name = nil, type = :required) | 
					
						
							| 
									
										
										
										
											2020-03-13 21:15:06 +00:00
										 |  |  |         name_match = if name | 
					
						
							|  |  |  |           false | 
					
						
							| 
									
										
										
										
											2017-05-30 01:22:47 +05:30
										 |  |  |         else | 
					
						
							| 
									
										
										
										
											2020-03-13 21:15:06 +00:00
										 |  |  |           true # Match only by type when name is nil | 
					
						
							| 
									
										
										
										
											2017-05-30 01:22:47 +05:30
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         case type | 
					
						
							| 
									
										
										
										
											2017-05-24 00:07:06 +05:30
										 |  |  |         when :required | 
					
						
							| 
									
										
										
										
											2017-08-06 14:48:39 +05:30
										 |  |  |           type_match = required_dependency?(node) | 
					
						
							| 
									
										
										
										
											2018-01-07 14:08:58 +00:00
										 |  |  |           name_match ||= required_dependency_name?(node, name) if type_match | 
					
						
							| 
									
										
										
										
											2018-03-19 10:11:08 +00:00
										 |  |  |         when :build, :test, :optional, :recommended | 
					
						
							| 
									
										
										
										
											2017-08-06 14:48:39 +05:30
										 |  |  |           type_match = dependency_type_hash_match?(node, type) | 
					
						
							| 
									
										
										
										
											2018-01-07 14:08:58 +00:00
										 |  |  |           name_match ||= dependency_name_hash_match?(node, name) if type_match | 
					
						
							|  |  |  |         when :any | 
					
						
							|  |  |  |           type_match = true | 
					
						
							|  |  |  |           name_match ||= required_dependency_name?(node, name) | 
					
						
							|  |  |  |           name_match ||= dependency_name_hash_match?(node, name) | 
					
						
							| 
									
										
										
										
											2017-05-24 21:07:50 +05:30
										 |  |  |         else | 
					
						
							|  |  |  |           type_match = false | 
					
						
							| 
									
										
										
										
											2017-05-24 00:07:06 +05:30
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2017-05-30 01:22:47 +05:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-12 02:19:31 +11:00
										 |  |  |         @offensive_node = node if type_match || name_match | 
					
						
							| 
									
										
										
										
											2017-05-30 01:22:47 +05:30
										 |  |  |         type_match && name_match | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-15 02:28:32 +02:00
										 |  |  |       def_node_search :required_dependency?, <<~EOS | 
					
						
							| 
									
										
										
										
											2017-10-21 03:12:50 +02:00
										 |  |  |         (send nil? :depends_on ({str sym} _)) | 
					
						
							| 
									
										
										
										
											2017-08-06 14:48:39 +05:30
										 |  |  |       EOS | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-15 02:28:32 +02:00
										 |  |  |       def_node_search :required_dependency_name?, <<~EOS | 
					
						
							| 
									
										
										
										
											2017-10-21 03:12:50 +02:00
										 |  |  |         (send nil? :depends_on ({str sym} %1)) | 
					
						
							| 
									
										
										
										
											2017-08-06 14:48:39 +05:30
										 |  |  |       EOS | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-15 02:28:32 +02:00
										 |  |  |       def_node_search :dependency_type_hash_match?, <<~EOS | 
					
						
							| 
									
										
										
										
											2017-08-06 14:48:39 +05:30
										 |  |  |         (hash (pair ({str sym} _) ({str sym} %1))) | 
					
						
							|  |  |  |       EOS | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-15 02:28:32 +02:00
										 |  |  |       def_node_search :dependency_name_hash_match?, <<~EOS | 
					
						
							| 
									
										
										
										
											2018-01-09 20:08:22 +00:00
										 |  |  |         (hash (pair ({str sym} %1) (...))) | 
					
						
							| 
									
										
										
										
											2017-08-06 14:48:39 +05:30
										 |  |  |       EOS | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # Return all the caveats' string nodes in an array. | 
					
						
							| 
									
										
										
										
											2017-05-22 13:09:49 +05:30
										 |  |  |       def caveats_strings | 
					
						
							|  |  |  |         find_strings(find_method_def(@body, :caveats)) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # Returns the sha256 str node given a sha256 call node. | 
					
						
							| 
									
										
										
										
											2017-06-16 19:44:14 +05:30
										 |  |  |       def get_checksum_node(call) | 
					
						
							|  |  |  |         return if parameters(call).empty? || parameters(call).nil? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-16 19:44:14 +05:30
										 |  |  |         if parameters(call).first.str_type? | 
					
						
							|  |  |  |           parameters(call).first | 
					
						
							|  |  |  |         # sha256 is passed as a key-value pair in bottle blocks | 
					
						
							|  |  |  |         elsif parameters(call).first.hash_type? | 
					
						
							| 
									
										
										
										
											2021-01-28 19:32:23 +01:00
										 |  |  |           if parameters(call).first.keys.first.value == :cellar | 
					
						
							|  |  |  |             # sha256 :cellar :any, :tag "hexdigest" | 
					
						
							|  |  |  |             parameters(call).first.values.last | 
					
						
							|  |  |  |           elsif parameters(call).first.keys.first.is_a?(RuboCop::AST::SymbolNode) | 
					
						
							|  |  |  |             # sha256 :tag "hexdigest" | 
					
						
							|  |  |  |             parameters(call).first.values.first | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             # Legacy bottle block syntax | 
					
						
							|  |  |  |             # sha256 "hexdigest" => :tag | 
					
						
							|  |  |  |             parameters(call).first.keys.first | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2017-06-16 19:44:14 +05:30
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # Yields to a block with comment text as parameter. | 
					
						
							| 
									
										
										
										
											2017-07-30 20:14:59 +05:30
										 |  |  |       def audit_comments | 
					
						
							| 
									
										
										
										
											2021-01-12 02:19:31 +11:00
										 |  |  |         processed_source.comments.each do |comment_node| | 
					
						
							| 
									
										
										
										
											2017-07-30 20:14:59 +05:30
										 |  |  |           @offensive_node = comment_node | 
					
						
							|  |  |  |           yield comment_node.text | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # Returns true if the formula is versioned. | 
					
						
							| 
									
										
										
										
											2017-05-24 13:04:55 +05:30
										 |  |  |       def versioned_formula? | 
					
						
							| 
									
										
										
										
											2017-08-30 15:48:41 +05:30
										 |  |  |         @formula_name.include?("@") | 
					
						
							| 
									
										
										
										
											2017-05-24 13:04:55 +05:30
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # Returns the formula tap. | 
					
						
							| 
									
										
										
										
											2017-08-04 01:18:00 +05:30
										 |  |  |       def formula_tap | 
					
						
							| 
									
										
										
										
											2021-02-12 18:33:37 +05:30
										 |  |  |         return unless (match_obj = @file_path.match(%r{/(homebrew-\w+)/})) | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-04 01:18:00 +05:30
										 |  |  |         match_obj[1] | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-27 13:13:09 -05:00
										 |  |  |       # Returns whether the given formula exists in the given style exception list. | 
					
						
							|  |  |  |       # Defaults to the current formula being checked. | 
					
						
							|  |  |  |       def tap_style_exception?(list, formula = nil) | 
					
						
							| 
									
										
										
										
											2020-11-29 15:17:11 -05:00
										 |  |  |         if @tap_style_exceptions.nil? && !formula_tap.nil? | 
					
						
							| 
									
										
										
										
											2020-11-27 01:23:07 -05:00
										 |  |  |           @tap_style_exceptions = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           style_exceptions_dir = "#{File.dirname(File.dirname(@file_path))}/style_exceptions/*.json" | 
					
						
							|  |  |  |           Pathname.glob(style_exceptions_dir).each do |exception_file| | 
					
						
							|  |  |  |             list_name = exception_file.basename.to_s.chomp(".json").to_sym | 
					
						
							|  |  |  |             list_contents = begin | 
					
						
							|  |  |  |               JSON.parse exception_file.read | 
					
						
							|  |  |  |             rescue JSON::ParserError | 
					
						
							|  |  |  |               nil | 
					
						
							|  |  |  |             end | 
					
						
							| 
									
										
										
										
											2020-11-29 15:17:11 -05:00
										 |  |  |             next if list_contents.nil? || list_contents.count.zero? | 
					
						
							| 
									
										
										
										
											2020-11-27 01:23:07 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |             @tap_style_exceptions[list_name] = list_contents | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-29 15:17:11 -05:00
										 |  |  |         return false if @tap_style_exceptions.nil? || @tap_style_exceptions.count.zero? | 
					
						
							| 
									
										
										
										
											2020-11-27 01:23:07 -05:00
										 |  |  |         return false unless @tap_style_exceptions.key? list | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-27 13:13:09 -05:00
										 |  |  |         @tap_style_exceptions[list].include?(formula || @formula_name) | 
					
						
							| 
									
										
										
										
											2020-11-27 01:23:07 -05:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-23 04:09:13 +05:30
										 |  |  |       private | 
					
						
							| 
									
										
										
										
											2017-03-02 20:26:29 +05:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-24 00:07:06 +05:30
										 |  |  |       def formula_class?(node) | 
					
						
							|  |  |  |         _, class_node, = *node | 
					
						
							| 
									
										
										
										
											2017-09-04 13:47:05 +05:30
										 |  |  |         class_names = %w[
 | 
					
						
							|  |  |  |           Formula | 
					
						
							|  |  |  |           GithubGistFormula | 
					
						
							|  |  |  |           ScriptFileFormula | 
					
						
							|  |  |  |           AmazonWebServicesFormula | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         class_node && class_names.include?(string_content(class_node)) | 
					
						
							| 
									
										
										
										
											2017-04-23 04:09:13 +05:30
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2017-03-16 23:49:43 +05:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-04 01:18:00 +05:30
										 |  |  |       def file_path_allowed? | 
					
						
							| 
									
										
										
										
											2017-04-23 04:09:13 +05:30
										 |  |  |         paths_to_exclude = [%r{/Library/Homebrew/compat/}, | 
					
						
							|  |  |  |                             %r{/Library/Homebrew/test/}] | 
					
						
							| 
									
										
										
										
											2019-04-08 12:47:15 -04:00
										 |  |  |         return true if @file_path.nil? # file_path is nil when source is directly passed to the cop, e.g. in specs | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-04 01:18:00 +05:30
										 |  |  |         @file_path !~ Regexp.union(paths_to_exclude) | 
					
						
							| 
									
										
										
										
											2017-03-02 20:26:29 +05:30
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |