repo_description: Node.js JavaScript runtime ✨🐢🚀✨
repo_topics: javascript js linux macos mit node nodejs runtime windows
code: /* eslint jsdoc/require-jsdoc: "error" */
  
  'use strict';
  
  const {
    ArrayIsArray,
    ArrayPrototypeIncludes,
    ArrayPrototypeJoin,
    ArrayPrototypeMap,
    NumberIsFinite,
    NumberIsInteger,
    NumberIsNaN,
    NumberMAX_SAFE_INTEGER,
    NumberMIN_SAFE_INTEGER,
    NumberParseInt,
    ObjectPrototypeHasOwnProperty,
    RegExpPrototypeExec,
    String,
    StringPrototypeToUpperCase,
    StringPrototypeTrim,
  } = primordials;
  
  const {
    codes: {
      ERR_INVALID_ARG_TYPE: { HideStackFramesError: ERR_INVALID_ARG_TYPE },
      ERR_INVALID_ARG_VALUE: { HideStackFramesError: ERR_INVALID_ARG_VALUE },
      ERR_INVALID_THIS: { HideStackFramesError: ERR_INVALID_THIS },
      ERR_OUT_OF_RANGE: { HideStackFramesError: ERR_OUT_OF_RANGE },
      ERR_SOCKET_BAD_PORT: { HideStackFramesError: ERR_SOCKET_BAD_PORT },
      ERR_UNKNOWN_SIGNAL: { HideStackFramesError: ERR_UNKNOWN_SIGNAL },
    },
    hideStackFrames,
  } = require('internal/errors');
  const { normalizeEncoding } = require('internal/util');
  const {
    isAsyncFunction,
    isArrayBufferView,
    isRegExp,
  } = require('internal/util/types');
  const { signals } = internalBinding('constants').os;
  
  /**
   * @param {*} value
   * @returns {boolean}
   */
  function isInt32(value) {
    return value === (value | 0);
  }
  
  /**
   * @param {*} value
   * @returns {boolean}
   */
  function isUint32(value) {
    return value === (value >>> 0);
  }
  
  const octalReg = /^[0-7]+$/;
  const modeDesc = 'must be a 32-bit unsigned integer or an octal string';
  
  /**
   * Parse and validate values that will be converted into mode_t (the S_*
   * constants). Only valid numbers and octal strings are allowed. They could be
   * converted to 32-bit unsigned integers or non-negative signed integers in the
   * C++ land, but any value higher than 0o777 will result in platform-specific
   * behaviors.
   * @param {*} value Values to be validated
   * @param {string} name Name of the argument
   * @param {number} [def] If specified, will be returned for invalid values
   * @returns {number}
   */
  function parseFileMode(value, name, def) {
    value ??= def;
    if (typeof value === 'string') {
      if (RegExpPrototypeExec(octalReg, value) === null) {
        throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc);
      }
      value = NumberParseInt(value, 8);
    }
  
    validateUint32(value, name);
    return value;
  }
  
  /**
   * @callback validateInteger
   * @param {*} value
   * @param {string} name
   * @param {number} [min]
   * @param {number} [max]
   * @returns {asserts value is number}
   */
  
  /** @type {validateInteger} */
  const validateInteger = hideStackFrames(
    (value, name, min = NumberMIN_SAFE_INTEGER, max = NumberMAX_SAFE_INTEGER) => {
      if (typeof value !== 'number')
        throw new ERR_INVALID_ARG_TYPE(name, 'number', value);
      if (!NumberIsInteger(value))
        throw new ERR_OUT_OF_RANGE(name, 'an integer', value);
      if (value < min || value > max)
        throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value);
    },
  );
  
  /**
   * @callback validateInt32
   * @param {*} value
   * @param {string} name
   * @param {number} [min]
   * @param {number} [max]
   * @returns {asserts value is number}
   */
  
  /** @type {validateInt32} */
  const validateInt32 = hideStackFrames(
    (value, name, min = -2147483648, max = 2147483647) => {
      // The defaults for min and max correspond to the limits of 32-bit integers.
      if (typeof value !== 'number') {
        throw new ERR_INVALID_ARG_TYPE(name, 'number', value);
      }
      if (!NumberIsInteger(value)) {
        throw new ERR_OUT_OF_RANGE(name, 'an integer', value);
      }
      if (value < min || value > max) {
        throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value);
      }
    },
  );
  
  /**
   * @callback validateUint32
   * @param {*} value
   * @param {string} name
   * @param {boolean} [positive=false]
   * @returns {asserts value is number}
   */
  
  /** @type {validateUint32} */
  const validateUint32 = hideStackFrames((value, name, positive = false) => {
    if (typeof value !== 'number') {
      throw new ERR_INVALID_ARG_TYPE(name, 'number', value);
    }
    if (!NumberIsInteger(value)) {
      throw new ERR_OUT_OF_RANGE(name, 'an integer', value);
    }
    const min = positive ? 1 : 0;
    // 2 ** 32 === 4294967296
    const max = 4_294_967_295;
    if (value < min || value > max) {
      throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value);
    }
  });
  
  /**
   * @callback validateString
   * @param {*} value
   * @param {string} name
   * @returns {asserts value is string}
   */
  
  /** @type {validateString} */
  const validateString = hideStackFrames((value, name) => {
    if (typeof value !== 'string')
      throw new ERR_INVALID_ARG_TYPE(name, 'string', value);
  });
  
  /**
   * @callback validateNumber
   * @param {*} value
   * @param {string} name
   * @param {number} [min]
   * @param {number} [max]
   * @returns {asserts value is number}
   */
  
  /** @type {validateNumber} */
  const validateNumber = hideStackFrames((value, name, min = undefined, max) => {
    if (typeof value !== 'number')
      throw new ERR_INVALID_ARG_TYPE(name, 'number', value);
  
    if ((min != null && value < min) || (max != null && value > max) ||
      ((min != null || max != null) && NumberIsNaN(value))) {
      throw new ERR_OUT_OF_RANGE(
        name,
        `${min != null ? `>= ${min}` : ''}${min != null && max != null ? ' && ' : ''}${max != null ? `<= ${max}` : ''}`,
        value);
    }
  });
  
  /**
   * @callback validateOneOf
   * @template T
   * @param {T} value
   * @param {string} name
   * @param {T[]} oneOf
   */
  
  /** @type {validateOneOf} */
  const validateOneOf = hideStackFrames((value, name, oneOf) => {
    if (!ArrayPrototypeIncludes(oneOf, value)) {
      const allowed = ArrayPrototypeJoin(
        ArrayPrototypeMap(oneOf, (v) =>
          (typeof v === 'string' ? `'${v}'` : String(v))),
        ', ');
      const reason = 'must be one of: ' + allowed;
      throw new ERR_INVALID_ARG_VALUE(name, value, reason);
    }
  });
  
  /**
   * @callback validateBoolean
   * @param {*} value
   * @param {string} name
   * @returns {asserts value is boolean}
   */
  
  /** @type {validateBoolean} */
  const validateBoolean = hideStackFrames((value, name) => {
    if (typeof value !== 'boolean')
      throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value);
  });
  
  const kValidateObjectNone = 0;
  const kValidateObjectAllowNullable = 1 << 0;
  const kValidateObjectAllowArray = 1 << 1;
  const kValidateObjectAllowFunction = 1 << 2;
  const kValidateObjectAllowObjects = kValidateObjectAllowArray |
    kValidateObjectAllowFunction;
  const kValidateObjectAllowObjectsAndNull = kValidateObjectAllowNullable |
    kValidateObjectAllowArray |
    kValidateObjectAllowFunction;
  
  /**
   * @callback validateObject
   * @param {*} value
   * @param {string} name
   * @param {number} [options]
   */
  
  /** @type {validateObject} */
  const validateObject = hideStackFrames(
    (value, name, options = kValidateObjectNone) => {
      if (options === kValidateObjectNone) {
        if (value === null || ArrayIsArray(value)) {
          throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);
        }
  
        if (typeof value !== 'object') {
          throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);
        }
      } else {
        const throwOnNullable = (kValidateObjectAllowNullable & options) === 0;
  
        if (throwOnNullable && value === null) {
          throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);
        }
  
        const throwOnArray = (kValidateObjectAllowArray & options) === 0;
  
        if (throwOnArray && ArrayIsArray(value)) {
          throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);
        }
  
        const throwOnFunction = (kValidateObjectAllowFunction & options) === 0;
        const typeofValue = typeof value;
  
        if (typeofValue !== 'object' && (throwOnFunction || typeofValue !== 'function')) {
          throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);
        }
      }
    });
  
  /**
   * @callback validateDictionary - We are using the Web IDL Standard definition
   *                                of "dictionary" here, which means any value
   *                                whose Type is either Undefined, Null, or
   *                                Object (which includes functions).
   * @param {*} value
   * @param {string} name
   * @see https://webidl.spec.whatwg.org/#es-dictionary
   * @see https://tc39.es/ecma262/#table-typeof-operator-results
   */
  
  /** @type {validateDictionary} */
  const validateDictionary = hideStackFrames(
    (value, name) => {
      if (value != null && typeof value !== 'object' && typeof value !== 'function') {
        throw new ERR_INVALID_ARG_TYPE(name, 'a dictionary', value);
      }
    });
  
  /**
   * @callback validateArray
   * @param {*} value
   * @param {string} name
   * @param {number} [minLength]
   * @returns {asserts value is any[]}
   */
  
  /** @type {validateArray} */
  const validateArray = hideStackFrames((value, name, minLength = 0) => {
    if (!ArrayIsArray(value)) {
      throw new ERR_INVALID_ARG_TYPE(name, 'Array', value);
    }
    if (value.length < minLength) {
      const reason = `must be longer than ${minLength}`;
      throw new ERR_INVALID_ARG_VALUE(name, value, reason);
    }
  });
  
  /**
   * @callback validateStringArray
   * @param {*} value
   * @param {string} name
   * @returns {asserts value is string[]}
   */
  
  /** @type {validateStringArray} */
  const validateStringArray = hideStackFrames((value, name) => {
    validateArray(value, name);
    for (let i = 0; i < value.length; ++i) {
      // Don't use validateString here for performance reasons, as
      // we would generate intermediate strings for the name.
      if (typeof value[i] !== 'string') {
        throw new ERR_INVALID_ARG_TYPE(`${name}[${i}]`, 'string', value[i]);
      }
    }
  });
  
  /**
   * @callback validateBooleanArray
   * @param {*} value
   * @param {string} name
   * @returns {asserts value is boolean[]}
   */
  
  /** @type {validateBooleanArray} */
  const validateBooleanArray = hideStackFrames((value, name) => {
    validateArray(value, name);
    for (let i = 0; i < value.length; ++i) {
      // Don't use validateBoolean here for performance reasons, as
      // we would generate intermediate strings for the name.
      if (value[i] !== true && value[i] !== false) {
        throw new ERR_INVALID_ARG_TYPE(`${name}[${i}]`, 'boolean', value[i]);
      }
    }
  });
  
  /**
   * @callback validateAbortSignalArray
   * @param {*} value
   * @param {string} name
   * @returns {asserts value is AbortSignal[]}
   */
  
  /** @type {validateAbortSignalArray} */
  function validateAbortSignalArray(value, name) {
    validateArray(value, name);
    for (let i = 0; i < value.length; i++) {
      const signal = value[i];
      const indexedName = `${name}[${i}]`;
      if (signal == null) {
        throw new ERR_INVALID_ARG_TYPE(indexedName, 'AbortSignal', signal);
      }
      validateAbortSignal(signal, indexedName);
    }
  }
  
  /**
   * @param {*} signal
   * @param {string} [name='signal']
   * @returns {asserts signal is keyof signals}
   */
  const validateSignalName = hideStackFrames((signal, name = 'signal') => {
    validateString(signal, name);
  
    if (signals[signal] === undefined) {
      if (signals[StringPrototypeToUpperCase(signal)] !== undefined) {
        throw new ERR_UNKNOWN_SIGNAL(signal +
                                     ' (signals must use all capital letters)');
      }
  
      throw new ERR_UNKNOWN_SIGNAL(signal);
    }
  });
  
  /**
   * @callback validateBuffer
   * @param {*} buffer
   * @param {string} [name='buffer']
   * @returns {asserts buffer is ArrayBufferView}
   */
  
  /** @type {validateBuffer} */
  const validateBuffer = hideStackFrames((buffer, name = 'buffer') => {
    if (!isArrayBufferView(buffer)) {
      throw new ERR_INVALID_ARG_TYPE(name,
                                     ['Buffer', 'TypedArray', 'DataView'],
                                     buffer);
    }
  });
  
  /**
   * @param {string} data
   * @param {string} encoding
   */
  const validateEncoding = hideStackFrames((data, encoding) => {
    const normalizedEncoding = normalizeEncoding(encoding);
    const length = data.length;
  
    if (normalizedEncoding === 'hex' && length % 2 !== 0) {
      throw new ERR_INVALID_ARG_VALUE('encoding', encoding,
                                      `is invalid for data of length ${length}`);
    }
  });
  
  /**
   * Check that the port number is not NaN when coerced to a number,
   * is an integer and that it falls within the legal range of port numbers.
   * @param {*} port
   * @param {string} [name='Port']
   * @param {boolean} [allowZero=true]
   * @returns {number}
   */
  const validatePort = hideStackFrames((port, name = 'Port', allowZero = true) => {
    if ((typeof port !== 'number' && typeof port !== 'string') ||
        (typeof port === 'string' && StringPrototypeTrim(port).length === 0) ||
        +port !== (+port >>> 0) ||
        port > 0xFFFF ||
        (port === 0 && !allowZero)) {
      throw new ERR_SOCKET_BAD_PORT(name, port, allowZero);
    }
    return port | 0;
  });
  
  /**
   * @callback validateAbortSignal
   * @param {*} signal
   * @param {string} name
   */
  
  /** @type {validateAbortSignal} */
  const validateAbortSignal = hideStackFrames((signal, name) => {
    if (signal !== undefined &&
        (signal === null ||
         typeof signal !== 'object' ||
         !('aborted' in signal))) {
      throw new ERR_INVALID_ARG_TYPE(name, 'AbortSignal', signal);
    }
  });
  
  /**
   * @callback validateFunction
   * @param {*} value
   * @param {string} name
   * @returns {asserts value is Function}
   */
  
  /** @type {validateFunction} */
  const validateFunction = hideStackFrames((value, name) => {
    if (typeof value !== 'function')
      throw new ERR_INVALID_ARG_TYPE(name, 'Function', value);
  });
  
  /**
   * @callback validatePlainFunction
   * @param {*} value
   * @param {string} name
   * @returns {asserts value is Function}
   */
  
  /** @type {validatePlainFunction} */
  const validatePlainFunction = hideStackFrames((value, name) => {
    if (typeof value !== 'function' || isAsyncFunction(value))
      throw new ERR_INVALID_ARG_TYPE(name, 'Function', value);
  });
  
  /**
   * @callback validateUndefined
   * @param {*} value
   * @param {string} name
   * @returns {asserts value is undefined}
   */
  
  /** @type {validateUndefined} */
  const validateUndefined = hideStackFrames((value, name) => {
    if (value !== undefined)
      throw new ERR_INVALID_ARG_TYPE(name, 'undefined', value);
  });
  
  /**
   * @template T
   * @param {T} value
   * @param {string} name
   * @param {T[]} union
   */
  function validateUnion(value, name, union) {
    if (!ArrayPrototypeIncludes(union, value)) {
      throw new ERR_INVALID_ARG_TYPE(name, `('${ArrayPrototypeJoin(union, '|')}')`, value);
    }
  }
  
  /*
    The rules for the Link header field are described here:
    https://www.rfc-editor.org/rfc/rfc8288.html#section-3
  
    This regex validates any string surrounded by angle brackets
    (not necessarily a valid URI reference) followed by zero or more
    link-params separated by semicolons.
  */
  const linkValueRegExp = /^(?:<[^>\r\n]*>)(?:\s*;\s*[^;"\s]+(?:=(")?[^;"\s]*\1)?)*$/;
  
  /**
   * @param {any} value
   * @param {string} name
   */
  const validateLinkHeaderFormat = hideStackFrames((value, name) => {
    if (
      typeof value === 'undefined' ||
      !RegExpPrototypeExec(linkValueRegExp, value)
    ) {
      throw new ERR_INVALID_ARG_VALUE(
        name,
        value,
        'must be an array or string of format "</styles.css>; rel=preload; as=style"',
      );
    }
  });
  
  /**
   * Validate provided `this` object by checking that it has specific own property
   * @param {any} object
   * @param {string|symbol} fieldKey
   * @param {string} className
   */
  const validateThisInternalField = hideStackFrames((object, fieldKey, className) => {
    if (typeof object !== 'object' || object === null || !ObjectPrototypeHasOwnProperty(object, fieldKey)) {
      throw new ERR_INVALID_THIS(className);
    }
  });
  
  /**
   * @param {any} hints
   * @returns {string}
   */
  const validateLinkHeaderValue = hideStackFrames((hints) => {
    if (typeof hints === 'string') {
      validateLinkHeaderFormat.withoutStackTrace(hints, 'hints');
      return hints;
    } else if (ArrayIsArray(hints)) {
      const hintsLength = hints.length;
      let result = '';
  
      if (hintsLength === 0) {
        return result;
      }
  
      for (let i = 0; i < hintsLength; i++) {
        const link = hints[i];
        validateLinkHeaderFormat.withoutStackTrace(link, 'hints');
        result += link;
  
        if (i !== hintsLength - 1) {
          result += ', ';
        }
      }
  
      return result;
    }
  
    throw new ERR_INVALID_ARG_VALUE(
      'hints',
      hints,
      'must be an array or string of format "</styles.css>; rel=preload; as=style"',
    );
  });
  
  /**
   * Validates a single ignore option element (string, RegExp, or Function).
   * @param {*} value
   * @param {string} name
   */
  const validateIgnoreOptionElement = hideStackFrames((value, name) => {
    if (typeof value === 'string') {
      if (value.length === 0)
        throw new ERR_INVALID_ARG_VALUE(name, value, 'must be a non-empty string');
      return;
    }
    if (isRegExp(value)) return;
    if (typeof value === 'function') return;
    throw new ERR_INVALID_ARG_TYPE(name, ['string', 'RegExp', 'Function'], value);
  });
  
  /**
   * Validates the ignore option for fs.watch.
   * @param {*} value
   * @param {string} name
   */
  const validateIgnoreOption = hideStackFrames((value, name) => {
    if (value == null) return;
    if (ArrayIsArray(value)) {
      for (let i = 0; i < value.length; i++) {
        validateIgnoreOptionElement(value[i], `${name}[${i}]`);
      }
      return;
    }
    validateIgnoreOptionElement(value, name);
  });
  
  // 1. Returns false for undefined and NaN
  // 2. Returns true for finite numbers
  // 3. Throws ERR_INVALID_ARG_TYPE for non-numbers
  // 4. Throws ERR_OUT_OF_RANGE for infinite numbers
  const validateFiniteNumber = hideStackFrames((number, name) => {
    // Common case
    if (number === undefined) {
      return false;
    }
  
    if (NumberIsFinite(number)) {
      return true; // Is a valid number
    }
  
    if (NumberIsNaN(number)) {
      return false;
    }
  
    validateNumber(number, name);
  
    // Infinite numbers
    throw new ERR_OUT_OF_RANGE(name, 'a finite number', number);
  });
  
  // 1. Returns def for number when it's undefined or NaN
  // 2. Returns number for finite numbers >= lower and <= upper
  // 3. Throws ERR_INVALID_ARG_TYPE for non-numbers
  // 4. Throws ERR_OUT_OF_RANGE for infinite numbers or numbers > upper or < lower
  const checkRangesOrGetDefault = hideStackFrames(
    (number, name, lower, upper, def) => {
      if (!validateFiniteNumber(number, name)) {
        return def;
      }
      if (number < lower || number > upper) {
        throw new ERR_OUT_OF_RANGE(name, `>= ${lower} and <= ${upper}`, number);
      }
      return number;
    },
  );
  
  module.exports = {
    isInt32,
    isUint32,
    parseFileMode,
    validateArray,
    validateStringArray,
    validateBooleanArray,
    validateAbortSignalArray,
    validateBoolean,
    validateBuffer,
    validateDictionary,
    validateEncoding,
    validateFunction,
    validateIgnoreOption,
    validateInt32,
    validateInteger,
    validateNumber,
    validateObject,
    kValidateObjectNone,
    kValidateObjectAllowNullable,
    kValidateObjectAllowArray,
    kValidateObjectAllowFunction,
    kValidateObjectAllowObjects,
    kValidateObjectAllowObjectsAndNull,
    validateOneOf,
    validatePlainFunction,
    validatePort,
    validateSignalName,
    validateString,
    validateUint32,
    validateUndefined,
    validateUnion,
    validateAbortSignal,
    validateLinkHeaderValue,
    validateThisInternalField,
    validateFiniteNumber,
    checkRangesOrGetDefault,
  };
  