# frozen_string_literal: true

module Puppet::Plugins
module SyntaxCheckers
  # The lookup **key** for the multibind containing syntax checkers used to syntax check embedded string in non
  # puppet DSL syntax.
  # @api public
  SYNTAX_CHECKERS_KEY = :'puppet::syntaxcheckers'

  # SyntaxChecker is a Puppet Extension Point for the purpose of extending Puppet with syntax checkers.
  # The intended use is to create a class derived from this class and then register it with the Puppet context
  #
  # Creating the Extension Class
  # ----------------------------
  # As an example, a class for checking custom xml (aware of some custom schemes) may be authored in
  # say a puppet module called 'exampleorg/xmldata'. The name of the class should start with `Puppetx::<user>::<module>`,
  # e.g. 'Puppetx::Exampleorg::XmlData::XmlChecker" and
  # be located in `lib/puppetx/exampleorg/xml_data/xml_checker.rb`. The Puppet Binder will auto-load this file when it
  # has a binding to the class `Puppetx::Exampleorg::XmlData::XmlChecker'
  # The Ruby Module `Puppetx` is created by Puppet, the remaining modules should be created by the loaded logic - e.g.:
  #
  # @example Defining an XmlChecker
  #   module Puppetx::Exampleorg
  #     module XmlData
  #       class XmlChecker < Puppetx::Puppetlabs::SyntaxCheckers::SyntaxChecker
  #         def check(text, syntax_identifier, acceptor, location_hash)
  #            # do the checking
  #         end
  #       end
  #     end
  #   end
  #
  # Implementing the check method
  # -----------------------------
  # The implementation of the {#check} method should naturally perform syntax checking of the given text/string and
  # produce found issues on the given `acceptor`. These can be warnings or errors. The method should return `false` if
  # any warnings or errors were produced (it is up to the caller to check for error/warning conditions and report them
  # to the user).
  #
  # Issues are reported by calling the given `acceptor`, which takes a severity (e.g. `:error`,
  # or `:warning), an {Puppet::Pops::Issues::Issue} instance, and a {Puppet::Pops::Adapters::SourcePosAdapter}
  # (which describes details about linenumber, position, and length of the problem area). Note that the
  # `location_info` given to the check method holds information about the location of the string in its *container*
  # (e.g. the source position of a Heredoc); this information can be used if more detailed information is not
  # available, or combined if there are more details (relative to the start of the checked string).
  #
  # @example Reporting an issue
  #    # create an issue with a symbolic name (that can serve as a reference to more details about the problem),
  #    # make the name unique
  #    issue = Puppet::Pops::Issues::issue(:EXAMPLEORG_XMLDATA_ILLEGAL_XML) { "syntax error found in xml text" }
  #    source_pos = Puppet::Pops::Adapters::SourcePosAdapter.new()
  #    source_pos.line = info[:line] # use this if there is no detail from the used parser
  #    source_pos.pos = info[:pos]   # use this pos if there is no detail from used parser
  #
  #    # report it
  #    acceptor.accept(Puppet::Pops::Validation::Diagnostic.new(:error, issue, info[:file], source_pos, {}))
  #
  # There is usually a cap on the number of errors/warnings that are presented to the user, this is handled by the
  # reporting logic, but care should be taken to not generate too many as the issues are kept in memory until
  # the checker returns. The acceptor may set a limit and simply ignore issues past a certain (high) number of reported
  # issues (this number is typically higher than the cap on issues reported to the user).
  #
  # The `syntax_identifier`
  # -----------------------
  # The extension makes use of a syntax identifier written in mime-style. This identifier can be something simple
  # as 'xml', or 'json', but can also consist of several segments joined with '+' where the most specific syntax variant
  # is placed first. When searching for a syntax checker; say for JSON having some special traits, say 'userdata', the
  # author of the text may indicate this as the text having the syntax "userdata+json" - when a checker is looked up it is
  # first checked if there is a checker for "userdata+json", if none is found, a lookup is made for "json" (since the text
  # must at least be valid json). The given identifier is passed to the checker (to allow the same checker to check for
  # several dialects/specializations).
  #
  # Use in Puppet DSL
  # -----------------
  # The Puppet DSL Heredoc support makes use of the syntax checker extension. A user of a
  # heredoc can specify the syntax in the heredoc tag, e.g.`@(END:userdata+json)`.
  #
  #
  # @abstract
  #
  class SyntaxChecker
    # Checks the text for syntax issues and reports them to the given acceptor.
    # This implementation is abstract, it raises {NotImplementedError} since a subclass should have implemented the
    # method.
    #
    # @param text [String] The text to check
    # @param syntax_identifier [String] The syntax identifier in mime style (e.g. 'json', 'json-patch+json', 'xml', 'myapp+xml'
    # @option location_info [String] :file The filename where the string originates
    # @option location_info [Integer] :line The line number identifying the location where the string is being used/checked
    # @option location_info [Integer] :position The position on the line identifying the location where the string is being used/checked
    # @return [Boolean] Whether the checked string had issues (warnings and/or errors) or not.
    # @api public
    #
    def check(text, syntax_identifier, acceptor, location_info)
      raise NotImplementedError, "The class #{self.class.name} should have implemented the method check()"
    end
  end
end
end
