rapid7/metasploit-framework

View on GitHub
lib/rubocop/cop/lint/module_disclosure_date_format.rb

Summary

Maintainability
A
55 mins
Test Coverage
# frozen_string_literal: true

module RuboCop
  module Cop
    module Lint
      class ModuleDisclosureDateFormat < Base
        extend AutoCorrector
        include Alignment

        # 2020-01-03
        REQUIRED_DATE_FORMAT = '%Y-%m-%d'
        CORRECTABLE_DATE_FORMATS = [
          # Jan 3 2020
          '%b %d %Y',
        ].freeze
        MSG = "Modules should specify a DisclosureDate with the required format '%<format>s', for example '%<example>s'"

        def_node_matcher :find_update_info_node, <<~PATTERN
          (def :initialize _args (begin (super $(send nil? {:update_info :merge_info} (lvar :info) (hash ...))) ...))
        PATTERN

        def_node_matcher :find_nested_update_info_node, <<~PATTERN
          (def :initialize _args (super $(send nil? {:update_info :merge_info} (lvar :info) (hash ...)) ...))
        PATTERN

        def on_def(node)
          update_info_node = find_update_info_node(node) || find_nested_update_info_node(node)
          return if update_info_node.nil?

          hash = update_info_node.arguments.find { |argument| hash_arg?(argument) }
          hash.each_pair do |key, value|
            next unless key.value == 'DisclosureDate'
            next if valid_disclosure_date?(value)

            add_offense(
              value,
              message: format(MSG, format: REQUIRED_DATE_FORMAT, example: DateTime.now.strftime(REQUIRED_DATE_FORMAT)),
              &autocorrector(value)
            )
          end
        end

        private

        def autocorrector(value_node)
          return nil unless correctable_disclosure_date?(value_node)

          lambda do |corrector|
            corrector.replace(value_node.loc.expression, "'#{format_disclosure_date!(value_node)}'")
          end
        end

        def valid_disclosure_date?(value_node)
          value_node.type == :str && Date.strptime(value_node.value, REQUIRED_DATE_FORMAT)
        rescue StandardError
          false
        end

        def correctable_disclosure_date?(value_node)
          format_disclosure_date!(value_node)
        rescue StandardError
          false
        end

        def format_disclosure_date!(value_node)
          CORRECTABLE_DATE_FORMATS.map do |format|
            begin
              Date.strptime(value_node.value, format).strftime(REQUIRED_DATE_FORMAT)
            rescue StandardError
              nil
            end
          end.compact.first
        end

        def hash_arg?(node)
          node.type == :hash
        end
      end
    end
  end
end