/** * @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 };