aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/rubocops/formula_desc_cop.rb
blob: 8a35e7d249ee9350e357ddd1cb1f94016f3bfc24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
require_relative "./extend/formula_cop"
require_relative "../extend/string"

module RuboCop
  module Cop
    module FormulaAuditStrict
      # This cop audits `desc` in Formulae
      #
      # - Checks for existence of `desc`
      # - Checks if size of `desc` > 80
      class DescLength < FormulaCop
        def audit_formula(_node, _class_node, _parent_class_node, body_node)
          desc_call = find_node_method_by_name(body_node, :desc)

          # Check if a formula's desc is present
          if desc_call.nil?
            problem "Formula should have a desc (Description)."
            return
          end

          # Check the formula's desc length. Should be >0 and <80 characters.
          desc = parameters(desc_call).first
          pure_desc_length = string_content(desc).length
          if pure_desc_length.zero?
            problem "The desc (description) should not be an empty string."
            return
          end

          desc_length = "#{@formula_name}: #{string_content(desc)}".length
          max_desc_length = 80
          return if desc_length <= max_desc_length
          problem "Description is too long. \"name: desc\" should be less than #{max_desc_length} characters. " \
                  "Length is calculated as #{@formula_name} + desc. (currently #{desc_length})"
        end
      end

      # This cop audits `desc` in Formulae
      #
      # - Checks if `desc` begins with an article
      # - Checks for correct usage of `command-line` in `desc`
      # - Checks description starts with a capital letter
      # - Checks if `desc` contains the formula name
      # - Checks if `desc` ends with a full stop (apart from in the case of "etc.")
      class Desc < FormulaCop
        VALID_LOWERCASE_WORDS = %w[
          ex
          eXtensible
          iOS
          macOS
          malloc
          ooc
          preexec
          x86
          xUnit
        ].freeze

        def audit_formula(_node, _class_node, _parent_class_node, body_node)
          desc_call = find_node_method_by_name(body_node, :desc)
          return if desc_call.nil?

          desc = parameters(desc_call).first

          # Check if command-line is wrongly used in formula's desc
          if match = regex_match_group(desc, /(command ?line)/i)
            c = match.to_s.chars.first
            problem "Description should use \"#{c}ommand-line\" instead of \"#{match}\""
          end

          # Check if a/an are used in a formula's desc
          if match = regex_match_group(desc, /^(an?)\s/i)
            problem "Description shouldn't start with an indefinite article i.e. \"#{match.to_s.strip}\""
          end

          # Check if invalid uppercase words are at the start of a
          # formula's desc
          if !VALID_LOWERCASE_WORDS.include?(string_content(desc).split.first) &&
             regex_match_group(desc, /^[a-z]/)
            problem "Description should start with a capital letter"
          end

          # Check if formula's desc starts with formula's name
          if regex_match_group(desc, /^#{@formula_name} /i)
            problem "Description shouldn't start with the formula name"
          end

          # Check if a full stop is used at the end of a formula's desc (apart from in the case of "etc.")
          return unless regex_match_group(desc, /\.$/) && !string_content(desc).end_with?("etc.")
          problem "Description shouldn't end with a full stop"
        end

        private

        def autocorrect(node)
          lambda do |corrector|
            correction = node.source
            first_word = string_content(node).split.first
            unless VALID_LOWERCASE_WORDS.include?(first_word)
              first_char = first_word.to_s.chars.first
              correction.sub!(/^(['"]?)([a-z])/, "\\1#{first_char.upcase}")
            end
            correction.sub!(/^(['"]?)an?\s/i, "\\1")
            correction.gsub!(/(ommand ?line)/i, "ommand-line")
            correction.gsub!(/(^|[^a-z])#{@formula_name}([^a-z]|$)/i, "\\1\\2")
            correction.gsub!(/^(['"]?)\s+/, "\\1")
            correction.gsub!(/\s+(['"]?)$/, "\\1")
            correction.gsub!(/\.(['"]?)$/, "\\1")
            corrector.insert_before(node.source_range, correction)
            corrector.remove(node.source_range)
          end
        end
      end
    end
  end
end