/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef frontend_ErrorReporter_h
#define frontend_ErrorReporter_h

#include "mozilla/Variant.h"

#include <optional>
#include <stdarg.h>  // for va_list
#include <stddef.h>  // for size_t
#include <stdint.h>  // for uint32_t

#include "js/ColumnNumber.h"  // JS::LimitedColumnNumberOneOrigin
#include "js/UniquePtr.h"
#include "vm/ErrorReporting.h"  // ErrorMetadata, ReportCompile{Error,Warning}

namespace JS {
class JS_PUBLIC_API ReadOnlyCompileOptions;
}

namespace js {
namespace frontend {

// An interface class to provide strictMode getter method, which is used by
// ErrorReportMixin::strictModeError* methods.
//
// This class is separated to be used as a back-channel from TokenStream to the
// strict mode flag which is available inside Parser, to avoid exposing the
// rest of SharedContext to TokenStream.
class StrictModeGetter {
 public:
  virtual bool strictMode() const = 0;
};

// This class provides error reporting methods, including warning, extra
// warning, and strict mode error.
//
// A class that inherits this class must provide the following methods:
//   * options
//   * getContext
//   * computeErrorMetadata
class ErrorReportMixin : public StrictModeGetter {
 public:
  // Returns a compile options (extra warning, warning as error) for current
  // compilation.
  virtual const JS::ReadOnlyCompileOptions& options() const = 0;

  // Returns the current context.
  virtual FrontendContext* getContext() const = 0;

  // A variant class for the offset of the error or warning.
  struct Current {};
  struct NoOffset {};
  using ErrorOffset = mozilla::Variant<uint32_t, Current, NoOffset>;

  // Fills ErrorMetadata fields for an error or warning at given offset.
  //   * offset is uint32_t if methods ending with "At" is called
  //   * offset is NoOffset if methods ending with "NoOffset" is called
  //   * offset is Current otherwise
  [[nodiscard]] virtual bool computeErrorMetadata(
      ErrorMetadata* err, const ErrorOffset& offset) const = 0;

  // ==== error ====
  //
  // Reports an error.
  //
  // Methods ending with "At" are for an error at given offset.
  // The offset is passed to computeErrorMetadata method and is transparent
  // for this class.
  //
  // Methods ending with "NoOffset" are for an error that doesn't correspond
  // to any offset. NoOffset is passed to computeErrorMetadata for them.
  //
  // Other methods except errorWithNotesAtVA are for an error at the current
  // offset. Current is passed to computeErrorMetadata for them.
  //
  // Methods contains "WithNotes" can be used if there are error notes.
  //
  // errorWithNotesAtVA is the actual implementation for all of above.
  // This can be called if the caller already has a va_list.

  void error(unsigned errorNumber, ...) const {
    va_list args;
    va_start(args, errorNumber);

    errorWithNotesAtVA(nullptr, mozilla::AsVariant(Current()), errorNumber,
                       &args);

    va_end(args);
  }
  void errorWithNotes(UniquePtr<JSErrorNotes> notes, unsigned errorNumber,
                      ...) const {
    va_list args;
    va_start(args, errorNumber);

    errorWithNotesAtVA(std::move(notes), mozilla::AsVariant(Current()),
                       errorNumber, &args);

    va_end(args);
  }
  void errorAt(uint32_t offset, unsigned errorNumber, ...) const {
    va_list args;
    va_start(args, errorNumber);

    errorWithNotesAtVA(nullptr, mozilla::AsVariant(offset), errorNumber, &args);

    va_end(args);
  }
  void errorWithNotesAt(UniquePtr<JSErrorNotes> notes, uint32_t offset,
                        unsigned errorNumber, ...) const {
    va_list args;
    va_start(args, errorNumber);

    errorWithNotesAtVA(std::move(notes), mozilla::AsVariant(offset),
                       errorNumber, &args);

    va_end(args);
  }
  void errorNoOffset(unsigned errorNumber, ...) const {
    va_list args;
    va_start(args, errorNumber);

    errorWithNotesAtVA(nullptr, mozilla::AsVariant(NoOffset()), errorNumber,
                       &args);

    va_end(args);
  }
  void errorWithNotesNoOffset(UniquePtr<JSErrorNotes> notes,
                              unsigned errorNumber, ...) const {
    va_list args;
    va_start(args, errorNumber);

    errorWithNotesAtVA(std::move(notes), mozilla::AsVariant(NoOffset()),
                       errorNumber, &args);

    va_end(args);
  }
  void errorWithNotesAtVA(UniquePtr<JSErrorNotes> notes,
                          const ErrorOffset& offset, unsigned errorNumber,
                          va_list* args) const {
    ErrorMetadata metadata;
    if (!computeErrorMetadata(&metadata, offset)) {
      return;
    }

    ReportCompileErrorLatin1VA(getContext(), std::move(metadata),
                               std::move(notes), errorNumber, args);
  }

  // ==== warning ====
  //
  // Reports a warning.
  //
  // Returns true if the warning is reported.
  // Returns false if the warning is treated as an error, or an error occurs
  // while reporting.
  //
  // See the comment on the error section for details on what the arguments
  // and function names indicate for all these functions.

  [[nodiscard]] bool warning(unsigned errorNumber, ...) const {
    va_list args;
    va_start(args, errorNumber);

    bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(Current()),
                                       errorNumber, &args);

    va_end(args);

    return result;
  }
  [[nodiscard]] bool warningAt(uint32_t offset, unsigned errorNumber,
                               ...) const {
    va_list args;
    va_start(args, errorNumber);

    bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(offset),
                                       errorNumber, &args);

    va_end(args);

    return result;
  }
  [[nodiscard]] bool warningNoOffset(unsigned errorNumber, ...) const {
    va_list args;
    va_start(args, errorNumber);

    bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(NoOffset()),
                                       errorNumber, &args);

    va_end(args);

    return result;
  }
  [[nodiscard]] bool warningWithNotesAtVA(UniquePtr<JSErrorNotes> notes,
                                          const ErrorOffset& offset,
                                          unsigned errorNumber,
                                          va_list* args) const {
    ErrorMetadata metadata;
    if (!computeErrorMetadata(&metadata, offset)) {
      return false;
    }

    return compileWarning(std::move(metadata), std::move(notes), errorNumber,
                          args);
  }

  // ==== strictModeError ====
  //
  // Reports an error if in strict mode code, or warn if not.
  //
  // Returns true if not in strict mode and a warning is reported.
  // Returns false if the error reported, or an error occurs while reporting.
  //
  // See the comment on the error section for details on what the arguments
  // and function names indicate for all these functions.

  [[nodiscard]] bool strictModeError(unsigned errorNumber, ...) const {
    va_list args;
    va_start(args, errorNumber);

    bool result = strictModeErrorWithNotesAtVA(
        nullptr, mozilla::AsVariant(Current()), errorNumber, &args);

    va_end(args);

    return result;
  }
  [[nodiscard]] bool strictModeErrorWithNotes(UniquePtr<JSErrorNotes> notes,
                                              unsigned errorNumber, ...) const {
    va_list args;
    va_start(args, errorNumber);

    bool result = strictModeErrorWithNotesAtVA(
        std::move(notes), mozilla::AsVariant(Current()), errorNumber, &args);

    va_end(args);

    return result;
  }
  [[nodiscard]] bool strictModeErrorAt(uint32_t offset, unsigned errorNumber,
                                       ...) const {
    va_list args;
    va_start(args, errorNumber);

    bool result = strictModeErrorWithNotesAtVA(
        nullptr, mozilla::AsVariant(offset), errorNumber, &args);

    va_end(args);

    return result;
  }
  [[nodiscard]] bool strictModeErrorWithNotesAt(UniquePtr<JSErrorNotes> notes,
                                                uint32_t offset,
                                                unsigned errorNumber,
                                                ...) const {
    va_list args;
    va_start(args, errorNumber);

    bool result = strictModeErrorWithNotesAtVA(
        std::move(notes), mozilla::AsVariant(offset), errorNumber, &args);

    va_end(args);

    return result;
  }
  [[nodiscard]] bool strictModeErrorNoOffset(unsigned errorNumber, ...) const {
    va_list args;
    va_start(args, errorNumber);

    bool result = strictModeErrorWithNotesAtVA(
        nullptr, mozilla::AsVariant(NoOffset()), errorNumber, &args);

    va_end(args);

    return result;
  }
  [[nodiscard]] bool strictModeErrorWithNotesNoOffset(
      UniquePtr<JSErrorNotes> notes, unsigned errorNumber, ...) const {
    va_list args;
    va_start(args, errorNumber);

    bool result = strictModeErrorWithNotesAtVA(
        std::move(notes), mozilla::AsVariant(NoOffset()), errorNumber, &args);

    va_end(args);

    return result;
  }
  [[nodiscard]] bool strictModeErrorWithNotesAtVA(UniquePtr<JSErrorNotes> notes,
                                                  const ErrorOffset& offset,
                                                  unsigned errorNumber,
                                                  va_list* args) const {
    if (!strictMode()) {
      return true;
    }

    ErrorMetadata metadata;
    if (!computeErrorMetadata(&metadata, offset)) {
      return false;
    }

    ReportCompileErrorLatin1VA(getContext(), std::move(metadata),
                               std::move(notes), errorNumber, args);
    return false;
  }

  // Reports a warning, or an error if the warning is treated as an error.
  [[nodiscard]] bool compileWarning(ErrorMetadata&& metadata,
                                    UniquePtr<JSErrorNotes> notes,
                                    unsigned errorNumber, va_list* args) const {
    return ReportCompileWarning(getContext(), std::move(metadata),
                                std::move(notes), errorNumber, args);
  }
};

// An interface class to provide miscellaneous methods used by error reporting
// etc.  They're mostly used by BytecodeCompiler, BytecodeEmitter, and helper
// classes for emitter.
class ErrorReporter : public ErrorReportMixin {
 public:
  // Returns Some(true) if the given offset is inside the given line
  // number `lineNum`, or Some(false) otherwise.
  //
  // Return None if an error happens.  This method itself doesn't report an
  // error, and any failure is supposed to be reported as OOM in the caller.
  virtual std::optional<bool> isOnThisLine(size_t offset,
                                           uint32_t lineNum) const = 0;

  // Returns the line number for given offset.
  virtual uint32_t lineAt(size_t offset) const = 0;

  // Returns the column number for given offset.
  virtual JS::LimitedColumnNumberOneOrigin columnAt(size_t offset) const = 0;
};

}  // namespace frontend
}  // namespace js

#endif  // frontend_ErrorReporter_h
