From 281aaca58541ec4261ba10fc8496f2e4c9c9c840 Mon Sep 17 00:00:00 2001 From: Daniil Sapa Date: Sat, 12 Oct 2024 13:12:42 +0300 Subject: [PATCH] Throw an error for the old config (#105) --- .changeset/olive-planets-retire.md | 5 +++ packages/steiger/src/models/config/index.ts | 6 ++-- ...scheme.spec.ts => validate-config.spec.ts} | 20 ++++++++++- ...alidation-scheme.ts => validate-config.ts} | 34 ++++++++++++++++--- 4 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 .changeset/olive-planets-retire.md rename packages/steiger/src/models/config/{build-validation-scheme.spec.ts => validate-config.spec.ts} (92%) rename packages/steiger/src/models/config/{build-validation-scheme.ts => validate-config.ts} (74%) diff --git a/.changeset/olive-planets-retire.md b/.changeset/olive-planets-retire.md new file mode 100644 index 0000000..87fd095 --- /dev/null +++ b/.changeset/olive-planets-retire.md @@ -0,0 +1,5 @@ +--- +'steiger': minor +--- + +Add error messages for invalid config shapes diff --git a/packages/steiger/src/models/config/index.ts b/packages/steiger/src/models/config/index.ts index d42bac1..c59e931 100644 --- a/packages/steiger/src/models/config/index.ts +++ b/packages/steiger/src/models/config/index.ts @@ -3,7 +3,7 @@ import { Config, GlobalIgnore, Plugin } from '@steiger/types' import createRuleInstructions from './create-rule-instructions' import { RuleInstructions } from './types' -import buildValidationScheme from './build-validation-scheme' +import { validateConfig } from './validate-config' import { isGlobalIgnore, isPlugin } from './raw-config' import { transformGlobs } from './transform-globs' @@ -32,9 +32,7 @@ export const $enabledRules = combine($ruleInstructions, $plugins, (ruleInstructi }) export function processConfiguration(rawConfig: Config, configLocationFolder: string | null) { - const validationScheme = buildValidationScheme(rawConfig) - const validatedConfig = validationScheme.parse(rawConfig) - + const validatedConfig = validateConfig(rawConfig) const plugins = rawConfig.filter(isPlugin) const configTransformedGlobs = transformGlobs(validatedConfig, configLocationFolder) const ruleInstructions = createRuleInstructions(configTransformedGlobs) diff --git a/packages/steiger/src/models/config/build-validation-scheme.spec.ts b/packages/steiger/src/models/config/validate-config.spec.ts similarity index 92% rename from packages/steiger/src/models/config/build-validation-scheme.spec.ts rename to packages/steiger/src/models/config/validate-config.spec.ts index cdbe44f..19d7bbc 100644 --- a/packages/steiger/src/models/config/build-validation-scheme.spec.ts +++ b/packages/steiger/src/models/config/validate-config.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' import { Config, Plugin } from '@steiger/types' -import buildValidationScheme from './build-validation-scheme' +import { buildValidationScheme, validateConfig } from './validate-config' import { isPlugin } from './raw-config' // The function maps a plugin object to a new object with the same properties as the original object, @@ -313,3 +313,21 @@ describe('buildValidationScheme', () => { expect(() => scheme.parse(config)).toThrow() }) }) + +describe('validateConfig', () => { + it('should throw an error when an old-style config is provided', () => { + // @ts-expect-error testing invalid input + expect(() => validateConfig({})).toThrow() + }) + + it('should throw an error when a config of wrong shape is provided ', () => { + // @ts-expect-error testing invalid input + expect(() => validateConfig('')).toThrow() + + // @ts-expect-error testing invalid input + expect(() => validateConfig(234234234)).toThrow() + + // @ts-expect-error testing invalid input + expect(() => validateConfig(true)).toThrow() + }) +}) diff --git a/packages/steiger/src/models/config/build-validation-scheme.ts b/packages/steiger/src/models/config/validate-config.ts similarity index 74% rename from packages/steiger/src/models/config/build-validation-scheme.ts rename to packages/steiger/src/models/config/validate-config.ts index 93c104f..1ce05c0 100644 --- a/packages/steiger/src/models/config/build-validation-scheme.ts +++ b/packages/steiger/src/models/config/validate-config.ts @@ -1,8 +1,16 @@ import z from 'zod' +import { BaseRuleOptions, Config, Plugin, Severity } from '@steiger/types' + import { getOptions, isConfigObject, isPlugin } from './raw-config' import { isEqual } from '../../shared/objects' -import { BaseRuleOptions, Config, Plugin, Severity } from '@steiger/types' + +const OLD_CONFIG_ERROR_MESSAGE = + 'Old configuration format detected. We are evolving!\nPlease follow this short guide to migrate to the new one:\nhttps://github.com/feature-sliced/steiger/blob/master/MIGRATION_GUIDE.md' +const WRONG_CONFIG_SHAPE_ERROR_MESSAGE = + 'The config should be an Array, but the provided config is not.\nHere is a link to the documentation that might help to fix it:\nhttps://github.com/feature-sliced/steiger?tab=readme-ov-file#configuration' +const NO_RULES_ERROR_MESSAGE = 'At least one rule must be provided by plugins!' +const NO_CONFIG_OBJECTS_ERROR_MESSAGE = 'At least one config object must be provided!' function getAllRuleNames(plugins: Array) { const allRules = plugins.flatMap((plugin) => plugin.ruleDefinitions) @@ -15,7 +23,7 @@ function validateConfigObjectsNumber(value: Config, ctx: z.RefinementCtx) { if (configObjects.length === 0) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'At least one config object must be provided!', + message: NO_CONFIG_OBJECTS_ERROR_MESSAGE, }) } } @@ -69,13 +77,13 @@ function validateRuleOptions(value: Config, ctx: z.RefinementCtx) { /** * Dynamically build a validation scheme based on the rules provided by plugins. * */ -export default function buildValidationScheme(rawConfig: Config) { +export function buildValidationScheme(rawConfig: Config) { const allRuleNames = getAllRuleNames(rawConfig.filter(isPlugin)) // Make sure there's at least one rule registered by plugins // Need to check this before creating the scheme, because zod.enum requires at least one element if (allRuleNames.length === 0) { - throw new Error('At least one rule must be provided by plugins!') + throw new Error(NO_RULES_ERROR_MESSAGE) } // Marked as "any" because return type is not useful for this validation @@ -124,3 +132,21 @@ export default function buildValidationScheme(rawConfig: Config) { .superRefine(validateRuleOptions) .superRefine(validateRuleUniqueness) } + +export function validateConfig(rawConfig: Config) { + const isOldConfig = typeof rawConfig === 'object' && !Array.isArray(rawConfig) + const isWrongShape = !Array.isArray(rawConfig) + + // Need to check the shape of the config separately before validating it, + // because building a validation scheme requires the config to be an array + if (isOldConfig) { + throw new Error(OLD_CONFIG_ERROR_MESSAGE) + } + + if (isWrongShape) { + throw new Error(WRONG_CONFIG_SHAPE_ERROR_MESSAGE) + } + + const validationScheme = buildValidationScheme(rawConfig) + return validationScheme.parse(rawConfig) +}