diff --git a/Library/Homebrew/rubocops/extend/formula_cop.rb b/Library/Homebrew/rubocops/extend/formula_cop.rb index 3f70086b3e..6215479b94 100644 --- a/Library/Homebrew/rubocops/extend/formula_cop.rb +++ b/Library/Homebrew/rubocops/extend/formula_cop.rb @@ -7,9 +7,9 @@ module RuboCop def on_class(node) file_path = processed_source.buffer.name return unless file_path_allowed?(file_path) - class_node, parent_class_node, body = *node - return unless formula_class?(parent_class_node) + return unless formula_class?(node) return unless respond_to?(:audit_formula) + class_node, parent_class_node, body = *node @formula_name = class_name(class_node) audit_formula(node, class_node, parent_class_node, body) end @@ -46,12 +46,61 @@ module RuboCop nil end - # Returns an array of method call nodes matching method_name inside node + # Set the given node as the offending node when required in custom cops + def offending_node(node) + @offensive_node = node + @offense_source_range = node.source_range + end + + # Returns an array of method call nodes matching method_name inside node with depth first order (Children nodes) def find_method_calls_by_name(node, method_name) return if node.nil? node.each_child_node(:send).select { |method_node| method_name == method_node.method_name } end + # Returns an array of method call nodes matching method_name in every descendant of node + def find_every_method_call_by_name(node, method_name) + return if node.nil? + node.each_descendant(:send).select { |method_node| method_name == method_node.method_name } + end + + # Returns nil if does not depend on dependency_name + # args: node - formula class' body node + # dependency_name - dependency's name + def depends_on?(node, dependency_name) + dependency_nodes = find_every_method_call_by_name(node, :depends_on) + idx = dependency_nodes.index do |n| + depends_on_name_type?(n, dependency_name, :required) || + depends_on_name_type?(n, dependency_name, :build) || + depends_on_name_type?(n, dependency_name, :optional) || + depends_on_name_type?(n, dependency_name, :recommended) || + depends_on_name_type?(n, dependency_name, :run) + end + return nil if idx.nil? + @offense_source_range = dependency_nodes[idx].source_range + @offensive_node = dependency_nodes[idx] + end + + # Returns true if given dependency name and dependency type exist in given dependency method call node + def depends_on_name_type?(node, dependency_name = nil, dependency_type = :required) + dependency_name_match = true if dependency_name.nil? # Match only by type + case dependency_type + when :required + dependency_type_match = !node.method_args.nil? && node.method_args.first.str_type? + dependency_name_match = (string_content(node.method_args.first) == dependency_name) if dependency_type_match + when :build || :optional || :recommended || :run + dependency_type_match = !node.method_args.nil? && + node.method_args.first.hash_type? && + node.method_args.first.values[0].children.first == dependency_type + dependency_name_match = (node.method_args.first.keys[0].children.first == dependency_name) if dependency_type_match + end + if dependency_type_match || dependency_name_match + @offensive_node = node + @offense_source_range = node.source_range + end + dependency_type_match && dependency_name_match + end + # Returns a block named block_name inside node def find_block(node, block_name) return if node.nil? @@ -112,6 +161,17 @@ module RuboCop false end + # Check if method_name is called among every descendant node of given node + def method_called_ever?(node, method_name) + node.each_descendant(:send) do |call_node| + next unless call_node.method_name == method_name + @offensive_node = call_node + @offense_source_range = call_node.source_range + return true + end + false + end + # Checks for precedence, returns the first pair of precedence violating nodes def check_precedence(first_nodes, next_nodes) next_nodes.each do |each_next_node| @@ -138,6 +198,17 @@ module RuboCop method_node.method_args end + # Returns true if the given parameters are present in method call + # and sets the method call as the offending node + def parameters_passed?(method_node, *params) + method_params = parameters(method_node) + @offensive_node = method_node + @offense_source_range = method_node.source_range + params.all? do |given_param| + method_params.any? { |method_param| given_param == string_content(method_param) } + end + end + # Returns the begin position of the node's line in source code def line_start_column(node) node.source_range.source_buffer.line_range(node.loc.line).begin_pos @@ -180,10 +251,16 @@ module RuboCop node.source_range.source_buffer end - # Returns the string representation if node is of type str(plain) or dstr(interpolated) + # Returns the string representation if node is of type str(plain) or dstr(interpolated) or const def string_content(node) - return node.str_content if node.type == :str - node.each_child_node(:str).map(&:str_content).join("") if node.type == :dstr + case node.type + when :str + return node.str_content if node.type == :str + when :dstr + return node.each_child_node(:str).map(&:str_content).join("") if node.type == :dstr + when :const + return node.const_name if node.type == :const + end end # Returns printable component name @@ -198,8 +275,9 @@ module RuboCop private - def formula_class?(parent_class_node) - parent_class_node && parent_class_node.const_name == "Formula" + def formula_class?(node) + _, class_node, = *node + class_node && string_content(class_node) == "Formula" end def file_path_allowed?(file_path)