Class: RuboCop::Cop::FormulaAudit::Miscellaneous Private

Inherits:
RuboCop::Cop::FormulaCop show all
Defined in:
rubocops/lines.rb,
sorbet/rbi/dsl/rubo_cop/cop/formula_audit/miscellaneous.rbi

Overview

This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.

This cop checks for other miscellaneous style violations.

Instance Attribute Summary

Attributes inherited from RuboCop::Cop::FormulaCop

#file_path

Instance Method Summary collapse

Methods inherited from RuboCop::Cop::FormulaCop

#audit_comments, #audit_urls, #caveats_strings, #dependency_name_hash_match?, #dependency_type_hash_match?, #depends_on?, #depends_on_name_type?, #formula_tap, #get_checksum_node, #on_class, #required_dependency?, #required_dependency_name?, #style_exceptions_dir, #tap_style_exception?, #versioned_formula?

Methods included from HelperFunctions

#block_method_called_in_block?, #check_precedence, #class_name, #component_precedes?, #end_column, #expression_negated?, #find_all_blocks, #find_block, #find_blocks, #find_const, #find_every_func_call_by_name, #find_every_method_call_by_name, #find_instance_call, #find_instance_method_call, #find_method_calls_by_name, #find_method_def, #find_method_with_args, #find_node_method_by_name, #find_strings, #format_component, #line_number, #line_start_column, #method_called?, #method_called_ever?, #method_name, #node_equals?, #offending_node, #parameters, #parameters_passed?, #problem, #regex_match_group, #source_buffer, #start_column, #string_content

Instance Method Details

#audit_formula(formula_nodes) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
# File 'rubocops/lines.rb', line 883

def audit_formula(formula_nodes)
  return if (body_node = formula_nodes.body_node).nil?

  # FileUtils is included in Formula
  # encfs modifies a file with this name, so check for some leading characters
  find_instance_method_call(body_node, "FileUtils", nil) do |method_node|
    problem "No need for `FileUtils.` before `#{method_node.method_name}`"
  end

  # Check for long inreplace block vars
  find_all_blocks(body_node, :inreplace) do |node|
    block_arg = node.arguments.children.first
    next if block_arg.source.size <= 1

    problem "`inreplace <filenames> do |s|` is preferred over `|#{block_arg.source}|`."
  end

  [:rebuild, :version_scheme].each do |method_name|
    find_method_with_args(body_node, method_name, 0) do
      problem "`#{method_name} 0` should be removed"
    end
  end

  find_instance_call(body_node, "ARGV") do |_method_node|
    problem "Use `build.with?` or `build.without?` instead of `ARGV` to check options"
  end

  find_instance_method_call(body_node, :man, :+) do |method|
    next unless (match = regex_match_group(parameters(method).fetch(0), /^man[1-8]$/))

    problem "`#{method.source}` should be `#{match[0]}`"
  end

  # Avoid hard-coding compilers
  find_every_method_call_by_name(body_node, :system).each do |method|
    param = parameters(method).fetch(0)
    if (match = regex_match_group(param, %r{^(/usr/bin/)?(gcc|clang|cc|c[89]9)(\s|$)}))
      problem "Use `\#{ENV.cc}` instead of hard-coding `#{match[2]}`"
    elsif (match = regex_match_group(param, %r{^(/usr/bin/)?((g|clang|c)\+\+)(\s|$)}))
      problem "Use `\#{ENV.cxx}` instead of hard-coding `#{match[2]}`"
    end
  end

  find_instance_method_call(body_node, "ENV", :[]=) do |method|
    param = parameters(method).fetch(1)
    if (match = regex_match_group(param, %r{^(/usr/bin/)?(gcc|clang|cc|c[89]9)(\s|$)}))
      problem "Use `\#{ENV.cc}` instead of hard-coding `#{match[2]}`"
    elsif (match = regex_match_group(param, %r{^(/usr/bin/)?((g|clang|c)\+\+)(\s|$)}))
      problem "Use `\#{ENV.cxx}` instead of hard-coding `#{match[2]}`"
    end
  end

  # Prefer formula path shortcuts in strings
  formula_path_strings(body_node, :share) do |p|
    next unless (match = regex_match_group(p, %r{^(/(man))/?}))

    problem "`\#{share}#{match[1]}` should be `\#{#{match[2]}}`"
  end

  formula_path_strings(body_node, :prefix) do |p|
    if (match = regex_match_group(p, %r{^(/share/(info|man))$}))
      problem ["`#", "{prefix}", match[1], '` should be `#{', match[2], "}`"].join
    end
    if (match = regex_match_group(p, %r{^((/share/man/)(man[1-8]))}))
      problem ["`#", "{prefix}", match[1], '` should be `#{', match[3], "}`"].join
    end
    if (match = regex_match_group(p, %r{^(/(bin|include|libexec|lib|sbin|share|Frameworks))}i))
      # match[2] must exist because of the previous line
      problem ["`#", "{prefix}", match[1], '` should be `#{', T.must(match[2]).downcase, "}`"].join
    end
  end

  find_every_method_call_by_name(body_node, :depends_on).each do |method|
    key, value = destructure_hash(parameters(method).fetch(0))
    next if key.nil? || value.nil?
    next unless (match = regex_match_group(value, /^(lua|perl|python|ruby)(\d*)/))

    problem "#{match[1]} modules should be vendored rather than using deprecated `#{method.source}`"
  end

  find_every_method_call_by_name(body_node, :system).each do |method|
    next unless (match = regex_match_group(parameters(method).fetch(0), /^(env|export)(\s+)?/))

    problem "Use `ENV` instead of invoking `#{match[1]}` to modify the environment"
  end

  find_every_method_call_by_name(body_node, :depends_on).each do |method|
    param = parameters(method).fetch(0)
    dep, option_child_nodes = hash_dep(param)
    next if dep.nil? || option_child_nodes.empty?

    option_child_nodes.each do |option|
      find_strings(option).each do |dependency|
        next unless (match = regex_match_group(dependency, /(with(out)?-\w+|c\+\+11)/))

        problem "Dependency '#{string_content(dep)}' should not use option `#{match[0]}`"
      end
    end
  end

  find_instance_method_call(body_node, :version, :==) do |method|
    next unless parameters_passed?(method, ["HEAD"])

    problem "Use `build.head?` instead of inspecting `version`"
  end

  find_instance_method_call(body_node, "ARGV", :include?) do |method|
    next unless parameters_passed?(method, ["--HEAD"])

    problem "Use `if build.head?` instead"
  end

  find_const(body_node, "MACOS_VERSION") do
    problem "Use `MacOS.version` instead of `MACOS_VERSION`"
  end

  find_const(body_node, "MACOS_FULL_VERSION") do
    problem "Use `MacOS.full_version` instead of `MACOS_FULL_VERSION`"
  end

  conditional_dependencies(body_node) do |node, method, param, dep_node|
    dep = string_content(dep_node)
    if node.if?
      if (method == :include? && regex_match_group(param, /^with-#{dep}$/)) ||
         (method == :with? && regex_match_group(param, /^#{dep}$/))
        offending_node(dep_node.parent)
        problem "Replace `#{node.source}` with `#{dep_node.parent.source} => :optional`"
      end
    elsif node.unless?
      if (method == :include? && regex_match_group(param, /^without-#{dep}$/)) ||
         (method == :without? && regex_match_group(param, /^#{dep}$/))
        offending_node(dep_node.parent)
        problem "Replace `#{node.source}` with `#{dep_node.parent.source} => :recommended`"
      end
    end
  end

  find_method_with_args(body_node, :fails_with, :llvm) do
    problem "`fails_with :llvm` is now a no-op and should be removed"
  end

  find_method_with_args(body_node, :needs, :openmp) do
    problem "`needs :openmp` should be replaced with `depends_on \"gcc\"`"
  end

  find_method_with_args(body_node, :system, /^(otool|install_name_tool|lipo)/) do
    problem "Use ruby-macho instead of calling #{T.must(@offensive_node).source}"
  end

  problem "Use new-style test definitions (`test do`)" if find_method_def(body_node, :test)

  find_method_with_args(body_node, :skip_clean, :all) do
    problem "`skip_clean :all` is deprecated; brew no longer strips symbols. " \
            "Pass explicit paths to prevent Homebrew from removing empty folders."
  end

  if find_method_def(processed_source.ast)
    raise "unexpected nil value for @offensive_node" unless @offensive_node

    problem "Define method `#{method_name(@offensive_node)}` in the class body, not at the top-level"
  end

  find_instance_method_call(body_node, "ENV", :runtime_cpu_detection) do
    next if tap_style_exception? :runtime_cpu_detection_allowlist

    problem "Formulae should be verified as having support for runtime hardware detection before " \
            "using `ENV.runtime_cpu_detection`."
  end

  find_every_method_call_by_name(body_node, :depends_on).each do |method|
    next unless method_called?(method, :new)

    problem "`depends_on` can take requirement classes instead of instances"
  end

  find_instance_method_call(body_node, "ENV", :[]) do |method|
    next unless modifier?(method.parent)

    param = parameters(method).first
    next unless node_equals?(param, "CI")

    problem 'Don\'t use `ENV["CI"]` for Homebrew CI checks.'
  end

  find_instance_method_call(body_node, "Dir", :[]) do |method|
    next if parameters(method).size != 1

    path = parameters(method).fetch(0)
    next unless path.str_type?
    next unless (match = regex_match_group(path, /^[^*{},]+$/))

    problem "`Dir([\"#{string_content(path)}\"])` is unnecessary; just use `#{match[0]}`"
  end

  fileutils_methods = Regexp.new(
    FileUtils.singleton_methods(false)
             .map { |m| "(?-mix:^#{Regexp.escape(m)}$)" }
             .join("|"),
  )
  find_every_method_call_by_name(body_node, :system).each do |method|
    param = parameters(method).fetch(0)
    next unless (match = regex_match_group(param, fileutils_methods))

    problem "Use the `#{match}` Ruby method instead of `#{method.source}`"
  end
end

#conditional_dependencies(node, *pattern, **kwargs, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (T.untyped)


17
# File 'sorbet/rbi/dsl/rubo_cop/cop/formula_audit/miscellaneous.rbi', line 17

def conditional_dependencies(node, *pattern, **kwargs, &block); end

#destructure_hash(node, **kwargs, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (T.untyped)


20
# File 'sorbet/rbi/dsl/rubo_cop/cop/formula_audit/miscellaneous.rbi', line 20

def destructure_hash(node, **kwargs, &block); end

#formula_path_strings(node, *pattern, **kwargs, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (T.untyped)


30
# File 'sorbet/rbi/dsl/rubo_cop/cop/formula_audit/miscellaneous.rbi', line 30

def formula_path_strings(node, *pattern, **kwargs, &block); end

#hash_dep(node, **kwargs, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (T.untyped)


33
# File 'sorbet/rbi/dsl/rubo_cop/cop/formula_audit/miscellaneous.rbi', line 33

def hash_dep(node, **kwargs, &block); end

#modifier?(node) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (Boolean)


1091
1092
1093
1094
1095
# File 'rubocops/lines.rb', line 1091

def modifier?(node)
  return false unless node.if_type?

  T.cast(node, RuboCop::AST::IfNode).modifier_form?
end