scope.js

const CaseParser = require('./caseParser');
const ErrorCase = require('./errorCase');
/**
 * A scope is just a group of cases and parsers Parserror can make use of.
 */
class Scope {
  /**
   * @param {string} name  The name of the scope.
   * @throws {TypeError} If the `name` is not a `string`.
   */
  constructor(name) {
    /**
     * The name of the scope.
     *
     * @type {string}
     * @access protected
     * @ignore
     */
    this._name = this._validateName(name);
    /**
     * The list of cases the scope manages.
     *
     * @type {ErrorCase[]}
     * @access protected
     * @ignore
     */
    this._cases = [];
    /**
     * A map of the parsers the scope has.
     *
     * @type {Object.<string, CaseParser>}
     * @access protected
     * @ignore
     */
    this._parsers = {};
  }
  /**
   * Adds a new case to the scope.
   *
   * @param {ErrorCase} theCase  The case to add.
   * @returns {Scope} For chaining purposes.
   * @throws {Error} If there's already a case with the same name on the scope.
   * @throws {Error} If `theCase` is not an instance of {@link ErrorCase}.
   */
  addCase(theCase) {
    this._validateCase(theCase);
    if (this.hasCase(theCase.name)) {
      throw new Error(
        `The case name '${theCase.name}' is already being used on the ` +
          `scope '${this._name}'`,
      );
    }

    this._cases.push(theCase);
    return this;
  }
  /**
   * Adds a reusable parser to the scope.
   *
   * @param {CaseParser} parser  The parser to add.
   * @returns {Scope} For chaining purposes.
   * @throws {Error} If there's already a parser with the same name on the scope.
   * @throws {Error} If `parser` is not an instance of {@link CaseParser}.
   */
  addParser(parser) {
    this._validateParser(parser);
    if (this.hasParser(parser.name)) {
      throw new Error(
        `The parser name '${parser.name}' is already being used on the ` +
          `scope '${this._name}'`,
      );
    }

    this._parsers[parser.name] = parser;
    return this;
  }
  /**
   * Returns a case by its name.
   *
   * @param {string}  name                  The name of the case.
   * @param {boolean} [failWithError=true]  Whether or not the method should throw an
   *                                        error if the case can't be found.
   * @returns {?ErrorCase}
   * @throws {Error} If `failWithError` is `true` and the case can't be found.
   */
  getCase(name, failWithError = true) {
    const theCase = this._cases.find((item) => item.name === name);
    if (!theCase && failWithError) {
      throw new Error(`The case '${name}' doesn't exist on the scope '${this._name}'`);
    }

    return theCase || null;
  }
  /**
   * Returns all available cases for this scope.
   *
   * @returns {ErrorCase[]}
   */
  getCases() {
    return this._cases;
  }
  /**
   * Returns a parser by its name.
   *
   * @param {string}  name                  The name of the parser.
   * @param {boolean} [failWithError=true]  Whether or not the method should throw an
   *                                        error if the parser can't be found.
   * @returns {?CaseParser}
   * @throws {Error} If `failWithError` is `true` and the parser can't be found.
   */
  getParser(name, failWithError = true) {
    const parser = this._parsers[name];
    if (!parser && failWithError) {
      throw new Error(`The parser '${name}' doesn't exist on the scope '${this._name}'`);
    }

    return parser || null;
  }
  /**
   * Checks whether or not there's a case based on its name.
   *
   * @param {string} name  The case's name.
   * @returns {boolean}
   */
  hasCase(name) {
    return this.getCase(name, false) !== null;
  }
  /**
   * Checks whether or not there's a parser based on its name.
   *
   * @param {string} name  The parser's name.
   * @returns {boolean}
   */
  hasParser(name) {
    return this.getParser(name, false) !== null;
  }
  /**
   * Removes a case from the scope.
   *
   * @param {string | ErrorCase} theCase  The name or the reference for the case to
   *                                      remove.
   * @returns {Scope} For chaining purposes.
   * @throws {Error} If the case doesn't exist on the scope.
   * @throws {Error} If `theCase` is a reference but is not an instance of
   *                 {@link ErrorCase}.
   */
  removeCase(theCase) {
    let name;
    if (typeof theCase === 'string') {
      name = theCase;
    } else {
      this._validateCase(theCase);
      ({ name } = theCase);
    }

    const newCases = this._cases.filter((item) => item.name !== name);
    if (newCases.length !== this._cases.length) {
      this._cases = newCases;
    } else {
      throw new Error(`The case '${name}' doesn't exist on the scope '${this._name}'`);
    }

    return this;
  }
  /**
   * Removes a parser from the scope.
   *
   * @param {string | CaseParser} parser  The name or the reference for the parser to
   *                                      remove.
   * @returns {Scope} For chaining purposes.
   * @throws {Error} If the parser doesn't exist on the scope.
   * @throws {Error} If `parser` is a reference but is not an instance of
   *                 {@link CaseParser}.
   */
  removeParser(parser) {
    let name;
    if (typeof parser === 'string') {
      name = parser;
    } else {
      this._validateParser(parser);
      ({ name } = parser);
    }

    if (this._parsers[name]) {
      const newParsers = { ...this._parsers };
      delete newParsers[name];
      this._parsers = newParsers;
    } else {
      throw new Error(`The parser '${name}' doesn't exist on the scope '${this._name}'`);
    }

    return this;
  }
  /**
   * The scope's name.
   *
   * @type {string}
   */
  get name() {
    return this._name;
  }
  /**
   * Validates if a case is an instance of {@link ErrorCase}.
   *
   * @param {ErrorCase} theCase  The case to validate.
   * @throws {TypeError} If `theCase` is not an instance of {@link ErrorCase}.
   * @access protected
   * @ignore
   */
  _validateCase(theCase) {
    if (!(theCase instanceof ErrorCase)) {
      throw new TypeError("The received case is not an instance of 'ErrorCase'");
    }
  }
  /**
   * Validates that the name the class intends to use is a `string`.
   *
   * @param {string} name  The name to validate.
   * @returns {string}
   * @throws {TypeError} If the `name` is not a string.
   * @access protected
   * @ignore
   */
  _validateName(name) {
    if (typeof name !== 'string') {
      throw new TypeError("The 'name' can only be a 'string'");
    }

    return name;
  }
  /**
   * Validates if a parser is an instance of {@link CaseParser}.
   *
   * @param {CaseParser} parser  The case to validate.
   * @throws {TypeError} If `parser` is not an instance of {@link CaseParser}.
   * @access protected
   * @ignore
   */
  _validateParser(parser) {
    if (!(parser instanceof CaseParser)) {
      throw new TypeError("The received parser is not an instance of 'CaseParser'");
    }
  }
}

module.exports = Scope;