/** * @fileoverview The schema to validate language options * @author Nicholas C. Zakas */ "use strict"; //----------------------------------------------------------------------------- // Data //----------------------------------------------------------------------------- const globalVariablesValues = new Set([ true, "true", "writable", "writeable", false, "false", "readonly", "readable", null, "off" ]); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** * Check if a value is a non-null object. * @param {any} value The value to check. * @returns {boolean} `true` if the value is a non-null object. */ function isNonNullObject(value) { return typeof value === "object" && value !== null; } /** * Check if a value is a non-null non-array object. * @param {any} value The value to check. * @returns {boolean} `true` if the value is a non-null non-array object. */ function isNonArrayObject(value) { return isNonNullObject(value) && !Array.isArray(value); } /** * Check if a value is undefined. * @param {any} value The value to check. * @returns {boolean} `true` if the value is undefined. */ function isUndefined(value) { return typeof value === "undefined"; } //----------------------------------------------------------------------------- // Schemas //----------------------------------------------------------------------------- /** * Validates the ecmaVersion property. * @param {string|number} ecmaVersion The value to check. * @returns {void} * @throws {TypeError} If the value is invalid. */ function validateEcmaVersion(ecmaVersion) { if (isUndefined(ecmaVersion)) { throw new TypeError("Key \"ecmaVersion\": Expected an \"ecmaVersion\" property."); } if (typeof ecmaVersion !== "number" && ecmaVersion !== "latest") { throw new TypeError("Key \"ecmaVersion\": Expected a number or \"latest\"."); } } /** * Validates the sourceType property. * @param {string} sourceType The value to check. * @returns {void} * @throws {TypeError} If the value is invalid. */ function validateSourceType(sourceType) { if (typeof sourceType !== "string" || !/^(?:script|module|commonjs)$/u.test(sourceType)) { throw new TypeError("Key \"sourceType\": Expected \"script\", \"module\", or \"commonjs\"."); } } /** * Validates the globals property. * @param {Object} globals The value to check. * @returns {void} * @throws {TypeError} If the value is invalid. */ function validateGlobals(globals) { if (!isNonArrayObject(globals)) { throw new TypeError("Key \"globals\": Expected an object."); } for (const key of Object.keys(globals)) { // avoid hairy edge case if (key === "__proto__") { continue; } if (key !== key.trim()) { throw new TypeError(`Key "globals": Global "${key}" has leading or trailing whitespace.`); } if (!globalVariablesValues.has(globals[key])) { throw new TypeError(`Key "globals": Key "${key}": Expected "readonly", "writable", or "off".`); } } } /** * Validates the parser property. * @param {Object} parser The value to check. * @returns {void} * @throws {TypeError} If the value is invalid. */ function validateParser(parser) { if (!parser || typeof parser !== "object" || (typeof parser.parse !== "function" && typeof parser.parseForESLint !== "function") ) { throw new TypeError("Key \"parser\": Expected object with parse() or parseForESLint() method."); } } /** * Validates the language options. * @param {Object} languageOptions The language options to validate. * @returns {void} * @throws {TypeError} If the language options are invalid. */ function validateLanguageOptions(languageOptions) { if (!isNonArrayObject(languageOptions)) { throw new TypeError("Expected an object."); } const { ecmaVersion, sourceType, globals, parser, parserOptions, ...otherOptions } = languageOptions; if ("ecmaVersion" in languageOptions) { validateEcmaVersion(ecmaVersion); } if ("sourceType" in languageOptions) { validateSourceType(sourceType); } if ("globals" in languageOptions) { validateGlobals(globals); } if ("parser" in languageOptions) { validateParser(parser); } if ("parserOptions" in languageOptions) { if (!isNonArrayObject(parserOptions)) { throw new TypeError("Key \"parserOptions\": Expected an object."); } } const otherOptionKeys = Object.keys(otherOptions); if (otherOptionKeys.length > 0) { throw new TypeError(`Unexpected key "${otherOptionKeys[0]}" found.`); } } module.exports = { validateLanguageOptions };