<commit_before>require 'nokogiri'
require 'rufus-lru'

module FoodCritic
  # Helper methods that form part of the Rules DSL.
  module Api
    include FoodCritic::AST
    include FoodCritic::XML

    include FoodCritic::Chef
    include FoodCritic::Notifications

    class RecursedTooFarError < StandardError; end

    # Find attribute access by type.
    def attribute_access(ast, options = {})
      options = { type: :any, ignore_calls: false }.merge!(options)
      return [] unless ast.respond_to?(:xpath)

      unless [:any, :string, :symbol, :vivified].include?(options[:type])
        fail ArgumentError, 'Node type not recognised'
      end

      case options[:type]
      when :any then
          vivified_attribute_access(ast, options) +
            standard_attribute_access(ast, options)
      when :vivified then
          vivified_attribute_access(ast, options)
      else
          standard_attribute_access(ast, options)
      end
    end

    # Does the specified recipe check for Chef Solo?
    def checks_for_chef_solo?(ast)
      raise_unless_xpath!(ast)
      # TODO: This expression is too loose, but also will fail to match other
      # types of conditionals.
      (!ast.xpath(%q{//*[self::if or self::ifop or self::unless]/
        *[self::aref or child::aref or self::call]
        [count(descendant::const[@value = 'Chef' or @value = 'Config']) = 2
          and
            (   count(descendant::ident[@value='solo']) > 0
            or  count(descendant::tstring_content[@value='solo']) > 0
            )
          ]}).empty?) ||
      ast.xpath('//if_mod[return][aref/descendant::ident/@value="solo"]/aref/
        const_path_ref/descendant::const').map do |c|
        c['value']
      end == %w(Chef Config)
    end

    # Is the
    # [chef-solo-search library](https://github.com/edelight/chef-solo-search)
    # available?
    def chef_solo_search_supported?(recipe_path)
      return false if recipe_path.nil? || !File.exist?(recipe_path)

      # Look for the chef-solo-search library.
      #
      # TODO: This will not work if the cookbook that contains the library
      # is not under the same `cookbook_path` as the cookbook being checked.
      cbk_tree_path = Pathname.new(File.join(recipe_path, '../../..'))
      search_libs = Dir[File.join(cbk_tree_path.realpath,
                                  '*/libraries/search.rb')]

      # True if any of the candidate library files match the signature:
      #
      #     class Chef
      #       def search
      search_libs.any? do |lib|
        !read_ast(lib).xpath(%q{//class[count(descendant::const[@value='Chef']
          ) = 1]/descendant::def/ident[@value='search']}).empty?
      end
    end

    # Support function to retrieve a metadata field
    def metadata_field(file, field)
      until (file.split(File::SEPARATOR) & standard_cookbook_subdirs).empty? do
        file = File.absolute_path(File.dirname(file.to_s))
      end
      file = File.dirname(file) unless File.extname(file).empty?

      md_path = File.join(file, 'metadata.rb')
      if File.exist?(md_path)
        value = read_ast(md_path).xpath("//stmts_add/
          command[ident/@value='#{field}']/
          descendant::tstring_content/@value").to_s
        raise RuntimeError, "Cant read name from #{md_path}" if value.to_s.empty?
        return value
      else
        raise RuntimeError, "Cant find #{md_path}"
      end
    end

    # The name of the cookbook containing the specified file.
    def cookbook_name(file)<commit_msg>Used metadata field in description when raising exception.<commit_after>require 'nokogiri'
require 'rufus-lru'

module FoodCritic
  # Helper methods that form part of the Rules DSL.
  module Api
    include FoodCritic::AST
    include FoodCritic::XML

    include FoodCritic::Chef
    include FoodCritic::Notifications

    class RecursedTooFarError < StandardError; end

    # Find attribute access by type.
    def attribute_access(ast, options = {})
      options = { type: :any, ignore_calls: false }.merge!(options)
      return [] unless ast.respond_to?(:xpath)

      unless [:any, :string, :symbol, :vivified].include?(options[:type])
        fail ArgumentError, 'Node type not recognised'
      end

      case options[:type]
      when :any then
          vivified_attribute_access(ast, options) +
            standard_attribute_access(ast, options)
      when :vivified then
          vivified_attribute_access(ast, options)
      else
          standard_attribute_access(ast, options)
      end
    end

    # Does the specified recipe check for Chef Solo?
    def checks_for_chef_solo?(ast)
      raise_unless_xpath!(ast)
      # TODO: This expression is too loose, but also will fail to match other
      # types of conditionals.
      (!ast.xpath(%q{//*[self::if or self::ifop or self::unless]/
        *[self::aref or child::aref or self::call]
        [count(descendant::const[@value = 'Chef' or @value = 'Config']) = 2
          and
            (   count(descendant::ident[@value='solo']) > 0
            or  count(descendant::tstring_content[@value='solo']) > 0
            )
          ]}).empty?) ||
      ast.xpath('//if_mod[return][aref/descendant::ident/@value="solo"]/aref/
        const_path_ref/descendant::const').map do |c|
        c['value']
      end == %w(Chef Config)
    end

    # Is the
    # [chef-solo-search library](https://github.com/edelight/chef-solo-search)
    # available?
    def chef_solo_search_supported?(recipe_path)
      return false if recipe_path.nil? || !File.exist?(recipe_path)

      # Look for the chef-solo-search library.
      #
      # TODO: This will not work if the cookbook that contains the library
      # is not under the same `cookbook_path` as the cookbook being checked.
      cbk_tree_path = Pathname.new(File.join(recipe_path, '../../..'))
      search_libs = Dir[File.join(cbk_tree_path.realpath,
                                  '*/libraries/search.rb')]

      # True if any of the candidate library files match the signature:
      #
      #     class Chef
      #       def search
      search_libs.any? do |lib|
        !read_ast(lib).xpath(%q{//class[count(descendant::const[@value='Chef']
          ) = 1]/descendant::def/ident[@value='search']}).empty?
      end
    end

    # Support function to retrieve a metadata field
    def metadata_field(file, field)
      until (file.split(File::SEPARATOR) & standard_cookbook_subdirs).empty? do
        file = File.absolute_path(File.dirname(file.to_s))
      end
      file = File.dirname(file) unless File.extname(file).empty?

      md_path = File.join(file, 'metadata.rb')
      if File.exist?(md_path)
        value = read_ast(md_path).xpath("//stmts_add/
          command[ident/@value='#{field}']/
          descendant::tstring_content/@value").to_s
        raise RuntimeError, "Cant read #{field} from #{md_path}" if value.to_s.empty?
        return value
      else
        raise RuntimeError, "Cant find #{md_path}"
      end
    end

    # The name of the cookbook containing the specified file.
    def cookbook_name(file)
{'fraction_non_alphanumeric': 0.1175639395223965, 'fraction_numerical': 0.0011304224954076585, 'mean_word_length': 2.6654583117555672, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6472464', 'n_tokens_mistral': 2210, 'n_tokens_neox': 2158, 'n_words': 532}