From b186968410977633c550a26c4a8c9d8da5598685 Mon Sep 17 00:00:00 2001 From: mika Date: Mon, 29 Jan 2024 10:19:21 +0100 Subject: [PATCH 1/9] Add configuration validation and refactor unknown identifiers --- README.md | 3 +- lib/Configuration.js | 112 +----- lib/__tests__/ImportStatements-test.js | 2 +- lib/__tests__/configurationSchema-test.js | 45 +++ lib/configurationSchema.js | 433 ++++++++++++++++++++++ package-lock.json | 97 ++++- package.json | 2 + 7 files changed, 566 insertions(+), 128 deletions(-) create mode 100644 lib/__tests__/configurationSchema-test.js create mode 100644 lib/configurationSchema.js diff --git a/README.md b/README.md index d22c63c..556292e 100644 --- a/README.md +++ b/README.md @@ -534,7 +534,6 @@ Available plugins are over at [Babel: Plugins List](https://babeljs.io/docs/plug #### Example: Remove all preconfigured defaults -```javascript parserPlugins: [] ``` @@ -571,7 +570,7 @@ parserPlugins: [ proposal: 'hack', }, ], -] +]; ``` ### `sortImports` diff --git a/lib/Configuration.js b/lib/Configuration.js index 892f6aa..03587ca 100644 --- a/lib/Configuration.js +++ b/lib/Configuration.js @@ -1,119 +1,23 @@ // @flow -import crypto from 'crypto'; import os from 'os'; import path from 'path'; import has from 'lodash/has'; -import globals from 'globals'; import semver from 'semver'; import FileUtils from './FileUtils'; import JsModule from './JsModule'; -import findPackageDependencies from './findPackageDependencies'; import meteorEnvironment from './environments/meteorEnvironment'; import nodeEnvironment from './environments/nodeEnvironment'; import normalizePath from './normalizePath'; import version from './version'; -import { DEFAULT_PARSER_PLUGINS } from './parse.js'; +import { validate, getDefaultConfig } from './configurationSchema.js'; const JSON_CONFIG_FILE = '.importjs.json'; const JS_CONFIG_FILES = ['.importjs.js', '.importjs.cjs', '.importjs.mjs']; -function findGlobalsFromEnvironments( - environments: Array, -): Array { - const result = Object.keys(globals.builtin); - - environments.forEach((environment: string) => { - const envGlobals = globals[environment]; - if (!envGlobals) { - return; - } - result.push(...Object.keys(envGlobals)); - }); - return result; -} - -const DEFAULT_CONFIG = { - aliases: {}, - declarationKeyword: 'import', - cacheLocation: ({ config }: Object): string => { - const hash = crypto - .createHash('md5') - .update(`${config.workingDirectory}-v4`) - .digest('hex'); - return path.join(os.tmpdir(), `import-js-${hash}.db`); - }, - coreModules: [], - namedExports: {}, - environments: [], - excludes: [], - globals: ({ config }: Object): Array => - findGlobalsFromEnvironments(config.get('environments')), - groupImports: true, - ignorePackagePrefixes: [], - importDevDependencies: false, - importFunction: 'require', - importStatementFormatter: ({ importStatement }: Object): string => - importStatement, - logLevel: 'info', - maxLineLength: 80, - minimumVersion: '0.0.0', - moduleNameFormatter: ({ moduleName }: Object): string => moduleName, - moduleSideEffectImports: (): Array => [], - sortImports: true, - emptyLineBetweenGroups: true, - stripFileExtensions: ['.js', '.jsx', '.ts', '.tsx'], - danglingCommas: true, - tab: ' ', - useRelativePaths: true, - packageDependencies: ({ config }: Object): Set => - findPackageDependencies( - config.workingDirectory, - config.get('importDevDependencies'), - ), - // Default configuration options, and options inherited from environment - // configuration are overridden if they appear in user config. Some options, - // however, get merged with the parent configuration. This list specifies which - // ones are merged. - mergableOptions: { - aliases: true, - coreModules: true, - namedExports: true, - globals: true, - }, - parserPlugins: DEFAULT_PARSER_PLUGINS, -}; - -const KNOWN_CONFIGURATION_OPTIONS = [ - 'aliases', - 'cacheLocation', - 'coreModules', - 'declarationKeyword', - 'environments', - 'excludes', - 'globals', - 'groupImports', - 'ignorePackagePrefixes', - 'importDevDependencies', - 'importFunction', - 'importStatementFormatter', - 'logLevel', - 'maxLineLength', - 'minimumVersion', - 'moduleNameFormatter', - 'moduleSideEffectImports', - 'namedExports', - 'sortImports', - 'stripFileExtensions', - 'tab', - 'useRelativePaths', - 'mergableOptions', - 'danglingCommas', - 'emptyLineBetweenGroups', - 'parserPlugins', -]; +const DEFAULT_CONFIG = getDefaultConfig(); const DEPRECATED_CONFIGURATION_OPTIONS = []; @@ -122,16 +26,12 @@ const ENVIRONMENTS = { meteor: meteorEnvironment, }; -function checkForUnknownConfiguration(config: Object): Array { +function checkConfiguration(config: Object): Array { const messages = []; - Object.keys(config).forEach((option: string) => { - if (KNOWN_CONFIGURATION_OPTIONS.indexOf(option) === -1) { - messages.push(`Unknown configuration: \`${option}\``); - } - }); + const result = validate(config); - return messages; + return result.messages; } function checkForDeprecatedConfiguration(config: Object): Array { @@ -242,7 +142,7 @@ export default class Configuration { if (userConfig) { this.configs.push(userConfig); - this.messages.push(...checkForUnknownConfiguration(userConfig)); + this.messages.push(...checkConfiguration(userConfig)); this.messages.push(...checkForDeprecatedConfiguration(userConfig)); // Add configurations for the environments specified in the user config diff --git a/lib/__tests__/ImportStatements-test.js b/lib/__tests__/ImportStatements-test.js index 9e6d9d5..057b3a6 100644 --- a/lib/__tests__/ImportStatements-test.js +++ b/lib/__tests__/ImportStatements-test.js @@ -119,7 +119,7 @@ describe('ImportStatements', () => { beforeEach(() => { FileUtils.__setFile(path.join(process.cwd(), '.importjs.js'), { environments: ['meteor'], - packageDependencies: new Set(['meteor/bar']), + packageDependencies: ['meteor/bar'], }); }); diff --git a/lib/__tests__/configurationSchema-test.js b/lib/__tests__/configurationSchema-test.js new file mode 100644 index 0000000..573bb76 --- /dev/null +++ b/lib/__tests__/configurationSchema-test.js @@ -0,0 +1,45 @@ +import { validate, getDefaultConfig } from '../configurationSchema.js'; + +it('should export defaults as an object', () => { + const config = getDefaultConfig(); + + expect(config.aliases).toEqual({}); +}); + +it('should validate successfully', () => { + const result = validate({ + aliases: { + _: 'third-party-libs/underscore', + }, + }); + expect(result.error).toEqual(false); + expect(result.messages).toEqual([]); +}); + +it('should notify about unknown identifiers, and remove them', () => { + const data = { + thisAintRight: 'better fail', + }; + const result = validate(data); + expect(result.error).toEqual(true); + expect(result.messages[0]).toEqual('Unknown configuration: `thisAintRight`'); + expect(data.hasOwnProperty('thisAintRight')).toBe(false); +}); + +it('should handle functions', () => { + const result = validate({ + aliases: () => ({ _: 'third-party-libs/underscore' }), + }); + expect(result.error).toEqual(false); +}); + +it('should notify about invalid identifiers, and remove them', () => { + const data = { + aliases: 123, + }; + const result = validate(data); + expect(result.error).toEqual(true); + expect(result.messages.length).toEqual(1); + expect(result.messages[0]).toEqual('Invalid configuration: `aliases`'); + expect(data.hasOwnProperty('aliases')).toBe(false); +}); diff --git a/lib/configurationSchema.js b/lib/configurationSchema.js new file mode 100644 index 0000000..3d12563 --- /dev/null +++ b/lib/configurationSchema.js @@ -0,0 +1,433 @@ +import Ajv from 'ajv'; +import ajvInstanceof from 'ajv-keywords/dist/keywords/instanceof'; +import globals from 'globals'; +import findPackageDependencies from './findPackageDependencies'; +import { DEFAULT_PARSER_PLUGINS } from './parse.js'; +import crypto from 'crypto'; +import path from 'path'; +import os from 'os'; + +const SCHEMA = { + type: 'object', + additionalProperties: false, + properties: { + aliases: { + default: {}, + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'object', + additionalProperties: { + type: 'string', + }, + }, + ], + }, + cacheLocation: { + instanceof: 'Function', + default: ({ config }) => { + const hash = crypto + .createHash('md5') + .update(`${config.workingDirectory}-v4`) + .digest('hex'); + return path.join(os.tmpdir(), `import-js-${hash}.db`); + }, + }, + coreModules: { + default: [], + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'array', + items: { + type: 'string', + }, + }, + ], + }, + danglingCommas: { + default: true, + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'boolean', + }, + ], + }, + declarationKeyword: { + default: 'import', + anyOf: [ + { + instanceof: 'Function', + }, + { + enum: ['var', 'const', 'import'], + }, + ], + }, + emptyLineBetweenGroups: { + default: true, + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'boolean', + }, + ], + }, + environments: { + default: [], + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'array', + items: { + enum: ['meteor', 'node', 'browser', 'jasmine', 'jest'], + }, + }, + ], + }, + excludes: { + default: [], + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'array', + items: { + type: 'string', + }, + }, + ], + }, + globals: { + default: ({ config }) => + findGlobalsFromEnvironments(config.get('environments')), + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'array', + items: { + type: 'string', + }, + }, + ], + }, + groupImports: { + default: true, + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'boolean', + }, + ], + }, + ignorePackagePrefixes: { + default: [], + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'array', + items: { + type: 'string', + }, + }, + ], + }, + importDevDependencies: { + default: false, + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'boolean', + }, + ], + }, + importFunction: { + default: 'require', + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'string', + }, + ], + }, + importStatementFormatter: { + instanceof: 'Function', + default: ({ importStatement }) => importStatement, + }, + logLevel: { + default: 'info', + anyOf: [ + { + instanceof: 'Function', + }, + { + default: 'info', + enum: ['debug', 'info', 'warn', 'error'], + }, + ], + }, + maxLineLength: { + default: 80, + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'integer', + minimum: 10, + }, + ], + }, + mergableOptions: { + type: 'object', + default: { + aliases: true, + coreModules: true, + namedExports: true, + globals: true, + }, + additionalProperties: false, + properties: { + aliases: { type: 'boolean' }, + coreModules: { type: 'boolean' }, + danglingCommas: { type: 'boolean' }, + declarationKeyword: { type: 'boolean' }, + environments: { type: 'boolean' }, + excludes: { type: 'boolean' }, + globals: { type: 'boolean' }, + groupImports: { type: 'boolean' }, + ignorePackagePrefixes: { type: 'boolean' }, + importDevDependencies: { type: 'boolean' }, + importFunction: { type: 'boolean' }, + importStatementFormatter: { type: 'boolean' }, + logLevel: { type: 'boolean' }, + maxLineLength: { type: 'boolean' }, + minimumVersion: { type: 'boolean' }, + moduleNameFormatter: { type: 'boolean' }, + namedExports: { type: 'boolean' }, + sortImports: { type: 'boolean' }, + stripFileExtensions: { type: 'boolean' }, + tab: { type: 'boolean' }, + useRelativePaths: { type: 'boolean' }, + }, + }, + minimumVersion: { + default: '0.0.0', + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'string', + }, + ], + }, + moduleNameFormatter: { + instanceof: 'Function', + default: ({ moduleName }) => moduleName, + }, + moduleSideEffectImports: { + instanceof: 'Function', + default: () => [], + }, + namedExports: { + default: {}, + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'object', + additionalProperties: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + ], + }, + packageDependencies: { + default: ({ config }) => + findPackageDependencies( + config.workingDirectory, + config.get('importDevDependencies'), + ), + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'array', + items: { + type: `string`, + }, + }, + ], + }, + parserPlugins: { + default: DEFAULT_PARSER_PLUGINS, + type: 'array', + items: { + anyOf: [ + { + type: 'string', + }, + { + type: 'array', + minItems: 2, + additionalItems: false, + items: [ + { + type: 'string', + }, + { + type: 'object', + additionalProperties: { + type: 'string', + }, + }, + ], + }, + ], + }, + }, + sortImports: { + default: true, + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'boolean', + }, + ], + }, + stripFileExtensions: { + default: ['.js', '.jsx', '.ts', '.tsx'], + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'array', + items: { + type: 'string', + }, + }, + ], + }, + tab: { + default: ' ', + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'string', + }, + ], + }, + useRelativePaths: { + default: true, + anyOf: [ + { + instanceof: 'Function', + }, + { + type: 'boolean', + }, + ], + }, + }, +}; + +function findGlobalsFromEnvironments(environments) { + const result = Object.keys(globals.builtin); + + environments.forEach((environment) => { + const envGlobals = globals[environment]; + if (!envGlobals) { + return; + } + result.push(...Object.keys(envGlobals)); + }); + return result; +} + +export function getDefaultConfig() { + return Object.entries(SCHEMA.properties).reduce((acc, [k, v]) => { + if (typeof v !== `object` || v === null) { + throw new Error(`Expected schema key '${k}' to be an object`); + } + if (v.hasOwnProperty('default')) { + acc[k] = v.default; + } + return acc; + }, {}); +} + +export function validate(data) { + const ajv = new Ajv(); + ajvInstanceof(ajv); + + const validate = ajv.compile(SCHEMA); + + if (!validate(data)) { + return { + error: true, + messages: validate.errors + .map((err) => { + // report unknown identifiers the same as we have done in the past + // so we can showcase that the move to Ajv didn't change previous + // behaviors + if (err.message === 'must NOT have additional properties') { + // remove the extraneous identifier so we avoid later errors in code + // instancePath is empty if root key + const rootKey = + err.instancePath.split('/')[1] || err.params.additionalProperty; + delete data[rootKey]; + + return ( + 'Unknown configuration: `' + + (err.instancePath ? err.instancePath + `.` : ``) + + err.params.additionalProperty + + '`' + ); + } else { + // remove the failing identifier so we avoid later errors in code + const rootKey = err.instancePath.split('/')[1]; + delete data[rootKey]; + + return 'Invalid configuration: `' + err.instancePath.slice(1) + '`'; + } + }) + .filter((val, index, a) => a.indexOf(val) === index) + .filter((it) => !!it), + }; + } + + return { error: false, messages: [] }; +} diff --git a/package-lock.json b/package-lock.json index 578f108..4e9862a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "@babel/plugin-syntax-jsx": "^7.7.4", "@babel/plugin-syntax-typescript": "^7.7.4", "@babel/runtime": "^7.7.6", + "ajv": "^8.12.0", + "ajv-keywords": "^5.1.0", "commander": "^4.0.1", "fb-watchman": "^2.0.1", "glob": "^7.1.6", @@ -2075,6 +2077,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -2091,6 +2110,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -4456,15 +4482,13 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "peer": true, + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -4472,6 +4496,17 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -6000,6 +6035,23 @@ "node": ">=10" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6152,6 +6204,13 @@ "node": ">=8" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -6800,9 +6859,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "peer": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -12307,11 +12364,9 @@ "dev": true }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "peer": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -13670,8 +13725,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -13933,6 +13986,14 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -15408,8 +15469,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "peer": true, "dependencies": { "punycode": "^2.1.0" } diff --git a/package.json b/package.json index 2cdec6e..36cf392 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,8 @@ "@babel/plugin-syntax-jsx": "^7.7.4", "@babel/plugin-syntax-typescript": "^7.7.4", "@babel/runtime": "^7.7.6", + "ajv": "^8.12.0", + "ajv-keywords": "^5.1.0", "commander": "^4.0.1", "fb-watchman": "^2.0.1", "glob": "^7.1.6", From ae686c28c6cd7d39c63c44368f56caf3f68cdb25 Mon Sep 17 00:00:00 2001 From: mika Date: Fri, 2 Feb 2024 12:39:27 +0100 Subject: [PATCH 2/9] Add missing 'type' when using 'enum' in configuration schema --- lib/configurationSchema.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/configurationSchema.js b/lib/configurationSchema.js index 3d12563..e7c9fe5 100644 --- a/lib/configurationSchema.js +++ b/lib/configurationSchema.js @@ -67,6 +67,7 @@ const SCHEMA = { instanceof: 'Function', }, { + type: 'string', enum: ['var', 'const', 'import'], }, ], @@ -91,6 +92,7 @@ const SCHEMA = { { type: 'array', items: { + type: 'string', enum: ['meteor', 'node', 'browser', 'jasmine', 'jest'], }, }, @@ -184,6 +186,7 @@ const SCHEMA = { }, { default: 'info', + type: 'string', enum: ['debug', 'info', 'warn', 'error'], }, ], @@ -273,7 +276,7 @@ const SCHEMA = { default: ({ config }) => findPackageDependencies( config.workingDirectory, - config.get('importDevDependencies'), + config.get('importDevDependencies') ), anyOf: [ { @@ -421,6 +424,8 @@ export function validate(data) { const rootKey = err.instancePath.split('/')[1]; delete data[rootKey]; + console.log(err); + return 'Invalid configuration: `' + err.instancePath.slice(1) + '`'; } }) From ff7f7670126d9b410919ab8a1c9477de3d4f0664 Mon Sep 17 00:00:00 2001 From: mika Date: Sat, 3 Feb 2024 11:59:46 +0100 Subject: [PATCH 3/9] Re-add blockquote in README previously removed in error --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 556292e..bc8b5e8 100644 --- a/README.md +++ b/README.md @@ -534,6 +534,7 @@ Available plugins are over at [Babel: Plugins List](https://babeljs.io/docs/plug #### Example: Remove all preconfigured defaults +```javascript parserPlugins: [] ``` From df7a8297a8daa102b4bde6ef3941ff0e63e1297a Mon Sep 17 00:00:00 2001 From: mika Date: Sat, 3 Feb 2024 12:03:04 +0100 Subject: [PATCH 4/9] single quotes, not backticks! --- lib/configurationSchema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/configurationSchema.js b/lib/configurationSchema.js index e7c9fe5..56047a7 100644 --- a/lib/configurationSchema.js +++ b/lib/configurationSchema.js @@ -285,7 +285,7 @@ const SCHEMA = { { type: 'array', items: { - type: `string`, + type: 'string', }, }, ], From 4014f1785ffbe54931096aede815212a0ea3098e Mon Sep 17 00:00:00 2001 From: mika Date: Sat, 3 Feb 2024 12:15:01 +0100 Subject: [PATCH 5/9] More friendly error message when checking default configuration --- lib/configurationSchema.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/configurationSchema.js b/lib/configurationSchema.js index 56047a7..40bd9ad 100644 --- a/lib/configurationSchema.js +++ b/lib/configurationSchema.js @@ -276,7 +276,7 @@ const SCHEMA = { default: ({ config }) => findPackageDependencies( config.workingDirectory, - config.get('importDevDependencies') + config.get('importDevDependencies'), ), anyOf: [ { @@ -383,7 +383,9 @@ function findGlobalsFromEnvironments(environments) { export function getDefaultConfig() { return Object.entries(SCHEMA.properties).reduce((acc, [k, v]) => { if (typeof v !== `object` || v === null) { - throw new Error(`Expected schema key '${k}' to be an object`); + throw new Error( + `Expected schema key '${k}' to be an object, but it was of type '${typeof v}'. Got: ${v}`, + ); } if (v.hasOwnProperty('default')) { acc[k] = v.default; From 77b80fbd98b026537bf42804cae4744dfd7b2d92 Mon Sep 17 00:00:00 2001 From: mika Date: Sat, 3 Feb 2024 12:36:43 +0100 Subject: [PATCH 6/9] Backticks to single quotes, console.error instead of log --- lib/configurationSchema.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/configurationSchema.js b/lib/configurationSchema.js index 40bd9ad..3f752f8 100644 --- a/lib/configurationSchema.js +++ b/lib/configurationSchema.js @@ -417,7 +417,7 @@ export function validate(data) { return ( 'Unknown configuration: `' + - (err.instancePath ? err.instancePath + `.` : ``) + + (err.instancePath ? err.instancePath + '.' : '') + err.params.additionalProperty + '`' ); @@ -426,13 +426,13 @@ export function validate(data) { const rootKey = err.instancePath.split('/')[1]; delete data[rootKey]; - console.log(err); + console.error(err); return 'Invalid configuration: `' + err.instancePath.slice(1) + '`'; } }) .filter((val, index, a) => a.indexOf(val) === index) - .filter((it) => !!it), + .filter(Boolean), }; } From 41085882708f1540af34515b7d291f3e185159f3 Mon Sep 17 00:00:00 2001 From: mika Date: Sat, 3 Feb 2024 12:38:41 +0100 Subject: [PATCH 7/9] Actually, let's not log it --- lib/configurationSchema.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/configurationSchema.js b/lib/configurationSchema.js index 3f752f8..8fc1ee8 100644 --- a/lib/configurationSchema.js +++ b/lib/configurationSchema.js @@ -426,8 +426,6 @@ export function validate(data) { const rootKey = err.instancePath.split('/')[1]; delete data[rootKey]; - console.error(err); - return 'Invalid configuration: `' + err.instancePath.slice(1) + '`'; } }) From ae42f655022ed1d88af5c799a180a6a594509267 Mon Sep 17 00:00:00 2001 From: mika Date: Sun, 4 Feb 2024 08:50:44 +0100 Subject: [PATCH 8/9] Remove unused variable 'messages' --- lib/Configuration.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/Configuration.js b/lib/Configuration.js index 03587ca..a38ff00 100644 --- a/lib/Configuration.js +++ b/lib/Configuration.js @@ -27,8 +27,6 @@ const ENVIRONMENTS = { }; function checkConfiguration(config: Object): Array { - const messages = []; - const result = validate(config); return result.messages; From ade2bda157b5c96e5221ee5ab1baf48e76c5ef86 Mon Sep 17 00:00:00 2001 From: mika Date: Sun, 4 Feb 2024 09:02:17 +0100 Subject: [PATCH 9/9] Improve error message in defaultConfig --- lib/configurationSchema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/configurationSchema.js b/lib/configurationSchema.js index 8fc1ee8..5c249f3 100644 --- a/lib/configurationSchema.js +++ b/lib/configurationSchema.js @@ -384,7 +384,7 @@ export function getDefaultConfig() { return Object.entries(SCHEMA.properties).reduce((acc, [k, v]) => { if (typeof v !== `object` || v === null) { throw new Error( - `Expected schema key '${k}' to be an object, but it was of type '${typeof v}'. Got: ${v}`, + `Expected the value at SCHEMA.${k} to be an object, but it was of type '${typeof v}'. Got: ${v}`, ); } if (v.hasOwnProperty('default')) {