text: # -------------------------------------------------------------------------
  #
  #  Part of the CodeChecker project, under the Apache License v2.0 with
  #  LLVM Exceptions. See LICENSE for license information.
  #  SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  #
  # -------------------------------------------------------------------------
  """
  List the checkers available in the analyzers.
  """
  
  
  import argparse
  import json
  import os
  import subprocess
  import sys
  from collections import defaultdict
  from typing import Dict, Iterable, Tuple
  
  from codechecker_report_converter import twodim
  
  from codechecker_analyzer import analyzer_context
  from codechecker_analyzer.analyzers import analyzer_types
  from codechecker_analyzer.analyzers.clangsa.analyzer import ClangSA
  from codechecker_analyzer.analyzers.clangtidy.analyzer import ClangTidy
  
  from codechecker_common import arg, logger
  from codechecker_common.output import USER_FORMATS
  from codechecker_common.checker_labels import CheckerLabels
  from codechecker_analyzer import env
  from codechecker_analyzer.analyzers.config_handler import CheckerState
  
  LOG = logger.get_logger('system')
  
  
  def get_diagtool_bin():
      """
      Return full path of diagtool.
  
      Select clang binary, check for a 'diagtool' binary next to the selected
      clang binary and return full path of this binary if it exists.
      """
      context = analyzer_context.get_context()
      clang_bin = context.analyzer_binaries.get(ClangSA.ANALYZER_NAME)
  
      if not clang_bin:
          return None
  
      # Resolve symlink.
      clang_bin = os.path.realpath(clang_bin)
  
      # Find diagtool next to the clang binary.
      diagtool_bin = os.path.join(os.path.dirname(clang_bin), 'diagtool')
      if os.path.exists(diagtool_bin):
          return diagtool_bin
  
      LOG.debug("'diagtool' can not be found next to the clang binary (%s)!",
                clang_bin)
  
  
  def get_warnings(env=None):
      """
      Returns list of warning flags by using diagtool.
      """
      diagtool_bin = get_diagtool_bin()
  
      if not diagtool_bin:
          return []
  
      try:
          result = subprocess.check_output(
              [diagtool_bin, 'tree'],
              env=env,
              universal_newlines=True,
              encoding="utf-8",
              errors="ignore")
          return [w[2:] for w in result.split() if w.startswith("-W")]
      except (subprocess.CalledProcessError, OSError):
          return []
  
  
  def get_argparser_ctor_args():
      """
      This method returns a dict containing the kwargs for constructing an
      argparse.ArgumentParser (either directly or as a subparser).
      """
  
      data_files_dir_path = analyzer_context.get_context().data_files_dir_path
      labels_dir_path = os.path.join(data_files_dir_path, 'config', 'labels')
      return {
          'prog': 'CodeChecker checkers',
          'formatter_class': arg.RawDescriptionDefaultHelpFormatter,
  
          # Description is shown when the command's help is queried directly
          'description': "Get the list of checkers available and their enabled "
                         "status in the supported analyzers.",
  
          # Epilogue is shown after the arguments when the help is queried
          # directly.
          'epilog': """
  The list of checkers that are enabled or disabled by default can be edited by
  editing "profile:default" labels in the directory '{}'.
  
  Example scenario: List checkers by labels
  -----------------------------------------
  List checkers in "sensitive" profile:
      CodeChecker checkers --label profile:sensitive
      CodeChecker checkers --profile sensitive
  
  List checkers in "HIGH" severity:
      CodeChecker checkers --label severity:HIGH
      CodeChecker checkers --severity HIGH
  
  List checkers covering str34-c SEI-CERT rule:
      CodeChecker checkers --label sei-cert:str-34-c
      CodeChecker checkers --guideline sei-cert:str34-c
  
  List checkers covering all SEI-CERT rules:
      CodeChecker checkers --label guideline:sei-cert
      CodeChecker checkers --guideline sei-cert
  
  List available profiles, guidelines and severities:
      CodeChecker checkers --profile
      CodeChecker checkers --guideline
      CodeChecker checkers --severity
  
  List labels and their available values:
      CodeChecker checkers --label
      CodeChecker checkers --label severity
  """.format(os.path.join(labels_dir_path)),
  
          # Help is shown when the "parent" CodeChecker command lists the
          # individual subcommands.
          'help': "List the checkers available for code analysis."
      }
  
  
  def add_arguments_to_parser(parser):
      """
      Add the subcommand's arguments to the given argparse.ArgumentParser.
      """
  
      parser.add_argument('--analyzers',
                          nargs='+',
                          dest='analyzers',
                          metavar='ANALYZER',
                          required=False,
                          choices=analyzer_types.supported_analyzers,
                          default=list(analyzer_types.supported_analyzers.
                                       keys()),
                          help="Show checkers only from the analyzers "
                               "specified.")
  
      if get_diagtool_bin():
          parser.add_argument('-w', '--warnings',
                              dest='show_warnings',
                              default=argparse.SUPPRESS,
                              action='store_true',
                              required=False,
                              help="Show available warning flags.")
  
      parser.add_argument('--details',
                          dest='details',
                          default=argparse.SUPPRESS,
                          action='store_true',
                          required=False,
                          help="Show details about the checker, such as "
                               "status, checker name, analyzer name, severity, "
                               "guidelines and description. Status shows if the "
                               "checker is enabled besides the given labels. "
                               "If the labels don't trigger a checker then the "
                               "status is determined by the analyzer tool.")
  
      parser.add_argument('--label',
                          nargs='?',
                          required=False,
                          default=argparse.SUPPRESS,
                          help="Filter checkers that are attached the given "
                               "label. The format of a label is "
                               "<label>:<value>. If no argument is given then "
                               "available labels are listed. If only <label> is "
                               "given then available values are listed.")
  
      parser.add_argument('--profile',
                          dest='profile',
                          nargs='?',
                          required=False,
                          default=argparse.SUPPRESS,
                          help="List checkers enabled by the selected profile. "
                               "If no argument is given then available profiles "
                               "are listed.")
  
      parser.add_argument('--guideline',
                          dest='guideline',
                          nargs='?',
                          required=False,
                          default=argparse.SUPPRESS,
                          help="List checkers that report on a specific "
                               "guideline. Without additional parameter, the "
                               "available guidelines and their corresponding "
                               "rules will be listed.")
  
      parser.add_argument('--severity',
                          dest='severity',
                          nargs='?',
                          required=False,
                          default=argparse.SUPPRESS,
                          help="List checkers with the given severity. Make "
                               "sure to indicate severity in capitals (e.g. "
                               "HIGH, MEDIUM, etc.) If no argument is given "
                               "then available severities are listed.")
  
      parser.add_argument('--checker-config',
                          dest='checker_config',
                          default=argparse.SUPPRESS,
                          action='store_true',
                          required=False,
                          help="Show checker configuration options for all "
                               "existing checkers supported by the analyzer. "
                               "These can be given to 'CodeChecker analyze "
                               "--checker-config'.")
  
      filters = parser.add_mutually_exclusive_group(required=False)
  
      # --only-enabled and --only-disabled flags are deprecated because they're
      # not used anymore. Earlier we listed all checkers that would be enabled
      # during analysis with the given filter set (--label, --profile,
      # --guideline) and that list contained default checkers among others which
      # were not explicitly enabled. (These could have been eliminated by
      # --only-enabled). Now we list only those checkers which fulfill the
      # conjunction of the provided labels, or we list all checkers if no filter
      # is given.
      filters.add_argument('--only-enabled',
                           dest='only_enabled',
                           default=argparse.SUPPRESS,
                           action='store_true',
                           help="DEPRECATED. Show only the enabled checkers.")
  
      filters.add_argument('--only-disabled',
                           dest='only_disabled',
                           default=argparse.SUPPRESS,
                           action='store_true',
                           help="DEPRECATED. Show only the disabled checkers.")
  
      parser.add_argument('-o', '--output',
                          dest='output_format',
                          required=False,
                          default='custom',
                          choices=USER_FORMATS + ['custom'],
                          help="The format to list the applicable checkers as.")
  
      logger.add_verbose_arguments(parser)
      parser.set_defaults(func=main)
  
  
  def __uglify(text: str) -> str:
      """
      csv and json format output contain this non human readable header string:
      no CamelCase and no space.
      """
      return text.lower().replace(' ', '_')
  
  
  def guideline_rules_for_checker(
      checker: str,
      context: analyzer_context.Context
  ) -> Dict[str, list]:
      """
      Returns the guideline and rules covered by the given checker. This
      function returns a dict which maps guideline names to the covered
      rules: { "sei-cert": ["arr30-c", ...], ... }.
      """
      result = defaultdict(list)
      labels = context.checker_labels.labels_of_checker(checker)
      guidelines = context.checker_labels.get_description('guideline')
  
      for label in labels:
          if label[0] in guidelines:
              result[label[0]].append(label[1])
  
      return result
  
  
  def __guideline_to_label(
      args: argparse.Namespace,
      cl: CheckerLabels
  ) -> str:
      """
      Transforms --guideline parameter as if they were given through --label.
      For example "--guideline sei-cert" is equivalent with
      "--label guideline:sei-cert" and "--guideline sei-cert:str38-c" is the same
      as "--label sei-cert:str38-c".
      """
      guidelines = []
      for analyzer in args.analyzers:
          guidelines.extend(cl.occurring_values('guideline', analyzer))
  
      if args.guideline in guidelines:
          return f'guideline:{args.guideline}'
      else:
          if args.guideline.find(':') == -1:
              LOG.error('--guideline parameter is either <guideline> or '
                        '<guideline>:<rule>')
              sys.exit(1)
          return args.guideline
  
  
  def __get_detailed_checker_info(
      args: argparse.Namespace,
      cl: CheckerLabels
  ) -> Dict[str, list]:
      """
      Returns a dictionary which maps analyzer names to the collection of their
      supported checkers. Checker information is described with tuples of this
      information: (status, checker name, analyzer name, description, labels).
      """
      context = analyzer_context.get_context()
  
      working_analyzers, _ = analyzer_types.check_supported_analyzers(
          analyzer_types.supported_analyzers,
          context)
  
      analyzer_config_map = analyzer_types.build_config_handlers(
          args, context, working_analyzers)
  
      analyzer_environment = env.extend(
          context.path_env_extra,
          context.ld_lib_path_extra)
  
      checker_info = defaultdict(list)
  
      for analyzer in working_analyzers:
          config_handler = analyzer_config_map.get(analyzer)
          analyzer_class = analyzer_types.supported_analyzers[analyzer]
  
          checkers = analyzer_class.get_analyzer_checkers(
              config_handler, analyzer_environment)
  
          profile_checkers = []
          if 'profile' in args:
              available_profiles = cl.get_description('profile')
  
              if args.profile not in available_profiles:
                  LOG.error("Checker profile '%s' does not exist!",
                            args.profile)
                  LOG.error("To list available profiles, use '--profile list'.")
                  sys.exit(1)
  
              profile_checkers.append((f'profile:{args.profile}', True))
  
          if 'label' in args:
              profile_checkers.extend((label, True) for label in args.label)
  
          if 'severity' in args:
              profile_checkers.append((f'severity:{args.severity}', True))
  
          if 'guideline' in args:
              profile_checkers.append((__guideline_to_label(args, cl), True))
  
          config_handler.initialize_checkers(
              context, checkers, profile_checkers)
  
          for checker, (state, description) in config_handler.checks().items():
              # severity = cl.severity(checker)
              # guideline = guideline_rules_for_checker(checker, context)
              # checker_info[analyzer].append(
              #     (state, checker, analyzer, severity, guideline, description))
              checker_info[analyzer].append(
                  (state, checker, analyzer, description,
                   sorted(cl.labels_of_checker(checker, analyzer))))
  
      if 'show_warnings' in args:
          for warning in get_warnings(analyzer_environment):
              warning = 'clang-diagnostic-' + warning
              # guideline = guideline_rules_for_checker(warning, context)
              # checker_info[ClangTidy.ANALYZER_NAME].append(
              #     (CheckerState.default, warning, ClangTidy.ANALYZER_NAME,
              #      'MEDIUM', guideline, ''))
              checker_info[ClangTidy.ANALYZER_NAME].append(
                  (CheckerState.default, warning, ClangTidy.ANALYZER_NAME, '',
                   sorted(cl.labels_of_checker(
                      warning, ClangTidy.ANALYZER_NAME))))
  
      return checker_info
  
  
  def __print_profiles(args: argparse.Namespace, cl: CheckerLabels):
      """
      Print checker profiles according to the command line arguments to the
      standard output.
      """
      if args.output_format == 'custom':
          args.output_format = 'rows'
  
      if 'details' in args:
          header = ['Profile name', 'Description']
          rows = cl.get_description('profile').items()
      else:
          header = ['Profile name']
          rows = [(key,) for key in cl.get_description('profile')]
  
      if args.output_format in ['csv', 'json']:
          header = list(map(__uglify, header))
  
      print(twodim.to_str(args.output_format, header, rows))
  
  
  def __print_severities(args: argparse.Namespace, cl: CheckerLabels):
      """
      Print checker severities according to the command line arguments to the
      standard output.
      """
      if args.output_format == 'custom':
          args.output_format = 'rows'
  
      if 'details' in args:
          header = ['Severity', 'Description']
          rows = cl.get_description('severity').items()
      else:
          header = ['Severity']
          rows = [(key,) for key in cl.get_description('severity')]
  
      if args.output_format in ['csv', 'json']:
          header = list(map(__uglify, header))
  
      print(twodim.to_str(args.output_format, header, rows))
  
  
  def __print_guidelines(args: argparse.Namespace, cl: CheckerLabels):
      """
      Print guidelines according to the command line arguments to the standard
      output.
      """
      if args.output_format == 'custom':
          args.output_format = 'rows'
  
      result = {}
  
      for guideline in cl.get_description('guideline'):
          result[guideline] = set(cl.occurring_values(guideline))
  
      header = ['Guideline', 'Rules']
      if args.output_format in ['csv', 'json']:
          header = list(map(__uglify, header))
  
      if args.output_format == 'json':
          rows = [(g, sorted(list(r))) for g, r in result.items()]
      else:
          rows = [(g, ', '.join(sorted(r))) for g, r in result.items()]
  
      if args.output_format == 'rows':
          for row in rows:
              print('Guideline: {}'.format(row[0]))
              print('Rules: {}'.format(row[1]))
      else:
          print(twodim.to_str(args.output_format, header, rows))
  
  
  def __print_labels(args: argparse.Namespace, cl: CheckerLabels):
      """
      Print labels according to the command line arguments to the standard
      output.
      """
      if args.output_format == 'custom':
          args.output_format = 'rows'
  
      header = ['Label']
      if args.output_format in ['csv', 'json']:
          header = list(map(__uglify, header))
  
      rows = list(map(lambda x: (x,), cl.labels()))
  
      print(twodim.to_str(args.output_format, header, rows))
  
  
  def __print_label_values(args: argparse.Namespace, cl: CheckerLabels):
      """
      If --label flag is given an argument which doesn't contain a colon (:) then
      we assume that the user intends to see the available values of that label:
      CodeChecker checkers --label severity
      """
      if args.output_format == 'custom':
          args.output_format = 'rows'
  
      header = ['Value']
      if args.output_format == 'custom':
          args.output_format = 'rows'
  
      rows = list(map(lambda x: (x,), cl.occurring_values(args.label)))
  
      if rows:
          print(twodim.to_str(args.output_format, header, rows))
      else:
          LOG.info(
              'Label "%s" doesn\'t exist. Use "CodeChecker checkers --label" '
              'command to list available labels.', args.label)
  
  
  def __format_row(row: Tuple) -> Tuple:
      """
      Perform some formatting of the detailed checker info.
      row -- A tuple with detailed checker info coming from
             __get_detailed_checker_info() function.
      """
      state = '+' if row[0] == CheckerState.enabled else \
          '-' if row[0] == CheckerState.disabled else \
          '?'
  
      labels = ', '.join(f'{k}:{v}' for k, v in row[4])
  
      return state, row[1], row[2], row[3], labels
  
  
  def __print_checkers_custom_format(checkers: Iterable):
      """
      This function prints a detailed view of the selected checkers in a custom
      format. Due to its customness it isn't implemented in module twodim.
      """
      for checker in checkers:
          if checker[0] == CheckerState.enabled:
              status = 'enabled'
          elif checker[0] == CheckerState.disabled:
              status = 'disabled'
          else:
              status = 'unknown'
  
          print(checker[1])
          print('  Status:', status)
          print('  Analyzer:', checker[2])
          print('  Description:', checker[3])
          print('  Labels:')
  
          for label in checker[4]:
              print(f'    {label[0]}:{label[1]}')
  
          print()
  
  
  def __print_checkers_json_format(checkers: Iterable, detailed: bool):
      """
      Print checker information in JSON format. This function is implemented to
      shorten __print_checkers(). JSON format is handled separately because its
      structure differs from other twodim formats.
      """
      def checker_info_dict(c):
          if c[0] == CheckerState.enabled:
              status = 'enabled'
          elif c[0] == CheckerState.disabled:
              status = 'disabled'
          else:
              status = 'unknown'
  
          return {
              'status': status,
              'name': c[1],
              'analyzer': c[2],
              'description': c[3],
              'labels': list(map(lambda x: f'{x[0]}:{x[1]}', c[4]))}
  
      if detailed:
          print(json.dumps([checker_info_dict(c) for c in checkers]))
      else:
          print(json.dumps([c[1] for c in checkers]))
  
  
  def __print_checkers(args: argparse.Namespace, cl: CheckerLabels):
      """
      Print checkers according to the command line arguments to the standard
      output.
      """
      labels = [args.label] if 'label' in args else []
  
      if 'profile' in args:
          labels.append(f'profile:{args.profile}')
  
      if 'guideline' in args:
          labels.append(__guideline_to_label(args, cl))
  
      if 'severity' in args:
          labels.append(f'severity:{args.severity}')
  
      checker_info = __get_detailed_checker_info(args, cl)
  
      result = []
      for analyzer in args.analyzers:
          if labels:
              checkers = cl.checkers_by_labels(labels, analyzer)
              result.extend(
                  filter(lambda x: x[1] in checkers, checker_info[analyzer]))
          else:
              result.extend(checker_info[analyzer])
  
      if args.output_format == 'custom':
          if result:
              __print_checkers_custom_format(result)
          else:
              LOG.info('No checkers with the given label found.')
          return
  
      if args.output_format == 'json':
          __print_checkers_json_format(result, 'details' in args)
          return
  
      if 'details' in args:
          header = ['Status', 'Name', 'Analyzer', 'Description', 'Labels']
          rows = list(map(__format_row, result))
      else:
          header = ['Name']
          rows = [[r[1]] for r in result]
  
      if args.output_format in ['csv', 'json']:
          header = list(map(__uglify, header))
  
      if rows:
          print(twodim.to_str(args.output_format, header, rows))
      else:
          LOG.info('No checkers with the given label found.')
  
  
  def __print_checker_config(args: argparse.Namespace):
      """
      Print checker config options according to the command line arguments to the
      standard output. The set of config options comes from the analyzers.
      """
      if args.output_format == 'custom':
          args.output_format = 'rows'
  
      context = analyzer_context.get_context()
      working_analyzers, errored = analyzer_types.check_supported_analyzers(
          args.analyzers,
          context)
      analyzer_types.check_available_analyzers(working_analyzers, errored)
  
      analyzer_environment = env.extend(context.path_env_extra,
                                        context.ld_lib_path_extra)
  
      analyzer_config_map = analyzer_types.build_config_handlers(
          args, context, working_analyzers)
  
      if 'details' in args:
          header = ['Option', 'Description']
      else:
          header = ['Option']
  
      if args.output_format in ['csv', 'json']:
          header = list(map(__uglify, header))
  
      rows = []
      analyzer_failures = []
      for analyzer in working_analyzers:
          config_handler = analyzer_config_map.get(analyzer)
          analyzer_class = analyzer_types.supported_analyzers[analyzer]
  
          configs = analyzer_class.get_checker_config(config_handler,
                                                      analyzer_environment)
          if not configs:
              analyzer_failures.append(analyzer)
              continue
  
          rows.extend((':'.join((analyzer, c[0])), c[1]) if 'details' in args
                      else (':'.join((analyzer, c[0])),) for c in configs)
  
      if rows:
          print(twodim.to_str(args.output_format, header, rows))
  
      analyzer_types.print_unsupported_analyzers(errored)
  
      if analyzer_failures:
          LOG.error("Failed to get checker configuration options for '%s' "
                    "analyzer(s)! Please try to upgrade your analyzer "
                    "version to use this feature.",
                    ', '.join(analyzer_failures))
          sys.exit(1)
  
  
  def main(args):
      """
      List the checkers available in the specified (or all supported) analyzers
      alongside with their description or enabled status in various formats.
      """
      # If the given output format is not 'table', redirect logger's output to
      # the stderr.
      logger.setup_logger(args.verbose if 'verbose' in args else None,
                          None if args.output_format == 'table' else 'stderr')
  
      cl = analyzer_context.get_context().checker_labels
  
      if 'profile' in args and not args.profile:
          __print_profiles(args, cl)
          return
  
      if 'severity' in args and not args.severity:
          __print_severities(args, cl)
          return
  
      if 'guideline' in args and not args.guideline:
          __print_guidelines(args, cl)
          return
  
      if 'label' in args and not args.label:
          __print_labels(args, cl)
          return
  
      if 'label' in args and ':' not in args.label:
          __print_label_values(args, cl)
          return
  
      if 'checker_config' in args:
          __print_checker_config(args)
          return
  
      __print_checkers(args, cl)
  