Skip to content

Commit

Permalink
feat: config validation (#2412)
Browse files Browse the repository at this point in the history
* feat: basic user config validation

* fix: simplify config resolution and fix issue #327

* fix: remove no longer needed function

* fix: disable some unwanted validations

* fix: improve config validation

* fix: remove redundant validation

* fix: use reduceRight instead of reverse

* fix: rollback some code

* fix: drop invalid type casts

* fix: rollback unnecessary changes

* fix: rollback config validation

* fix: add missing type-guards and restore order

* fix: one more order change

* fix: add one more missing type guard

* fix: remove unused types reference

* fix: add additional unit tests

* fix: add additional regression tests
- remove also unnecessary type check

* fix: remove more unnecessary code changes

* fix: correct order of merging plugins

* fix: add missing type check

* fix: remove invalid type check

* fix: remove redundant code

* feat: implement config validation

* fix: allow to use function as a rule
  • Loading branch information
armano2 committed Nov 29, 2021
1 parent fc5d869 commit c717202
Show file tree
Hide file tree
Showing 45 changed files with 693 additions and 2,024 deletions.
5 changes: 3 additions & 2 deletions @commitlint/cli/src/cli.ts
Expand Up @@ -7,14 +7,15 @@ import resolveGlobal from 'resolve-global';
import yargs, {Arguments} from 'yargs';
import util from 'util';

import {CliFlags, Seed} from './types';
import {CliFlags} from './types';
import {
LintOptions,
LintOutcome,
ParserOptions,
ParserPreset,
QualifiedConfig,
Formatter,
UserConfig,
} from '@commitlint/types';
import {CliError} from './cli-error';

Expand Down Expand Up @@ -364,7 +365,7 @@ function getEditValue(flags: CliFlags) {
return edit;
}

function getSeed(flags: CliFlags): Seed {
function getSeed(flags: CliFlags): UserConfig {
const n = (flags.extends || []).filter(
(i): i is string => typeof i === 'string'
);
Expand Down
5 changes: 0 additions & 5 deletions @commitlint/cli/src/types.ts
Expand Up @@ -18,8 +18,3 @@ export interface CliFlags {
_: (string | number)[];
$0: string;
}

export interface Seed {
extends?: string[];
parserPreset?: string;
}
45 changes: 45 additions & 0 deletions @commitlint/config-validator/package.json
@@ -0,0 +1,45 @@
{
"name": "@commitlint/config-validator",
"version": "15.0.0",
"description": "config validator for commitlint.config.js",
"main": "lib/validate.js",
"types": "lib/validate.d.ts",
"files": [
"lib/"
],
"scripts": {
"deps": "dep-check",
"pkg": "pkg-check --skip-import"
},
"engines": {
"node": ">=v12"
},
"repository": {
"type": "git",
"url": "https://github.com/conventional-changelog/commitlint.git",
"directory": "@commitlint/config-validator"
},
"bugs": {
"url": "https://github.com/conventional-changelog/commitlint/issues"
},
"homepage": "https://commitlint.js.org/",
"keywords": [
"conventional-changelog",
"commitlint",
"library",
"core"
],
"author": {
"name": "Mario Nebl",
"email": "hello@herebecode.com"
},
"license": "MIT",
"devDependencies": {
"@commitlint/utils": "^15.0.0"
},
"dependencies": {
"@commitlint/types": "^15.0.0",
"ajv": "^6.12.6"
},
"gitHead": "d829bf6260304ca8d6811f329fcdd1b6c50e9749"
}
134 changes: 134 additions & 0 deletions @commitlint/config-validator/src/__snapshots__/validate.test.ts.snap
@@ -0,0 +1,134 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`validation should fail for defaultIgnoresNotBoolean 1`] = `
"Commitlint configuration in defaultIgnoresNotBoolean.js is invalid:
- Property \\"defaultIgnores\\" has the wrong type - should be boolean.
"
`;

exports[`validation should fail for extendsAsObject 1`] = `
"Commitlint configuration in extendsAsObject.js is invalid:
- Property \\"extends\\" has the wrong type - should be array.
- Property \\"extends\\" has the wrong type - should be string.
- \\"extends\\" should match exactly one schema in oneOf. Value: {\\"test\\":1}.
"
`;

exports[`validation should fail for extendsWithFunction 1`] = `
"Commitlint configuration in extendsWithFunction.js is invalid:
- Property \\"extends[0]\\" has the wrong type - should be string.
- Property \\"extends\\" has the wrong type - should be string.
- \\"extends\\" should match exactly one schema in oneOf. Value: [null].
"
`;

exports[`validation should fail for formatterAsObject 1`] = `
"Commitlint configuration in formatterAsObject.js is invalid:
- Property \\"formatter\\" has the wrong type - should be string.
"
`;

exports[`validation should fail for helpUrlAsArray 1`] = `
"Commitlint configuration in helpUrlAsArray.js is invalid:
- Property \\"helpUrl\\" has the wrong type - should be string.
"
`;

exports[`validation should fail for helpUrlNotString 1`] = `
"Commitlint configuration in helpUrlNotString.js is invalid:
- Property \\"helpUrl\\" has the wrong type - should be string.
"
`;

exports[`validation should fail for ignoresFunction 1`] = `
"Commitlint configuration in ignoresFunction.js is invalid:
- Property \\"ignores\\" has the wrong type - should be array.
"
`;

exports[`validation should fail for ignoresNotFunction 1`] = `
"Commitlint configuration in ignoresNotFunction.js is invalid:
- \\"ignores[0]\\" should be a function. Value: 1.
"
`;

exports[`validation should fail for parserPreset 1`] = `
"Commitlint configuration in parserPreset.js is invalid:
- Property \\"parserPreset\\" has the wrong type - should be string.
- Property \\"parserPreset\\" has the wrong type - should be object.
- \\"parserPreset\\" should match exactly one schema in oneOf. Value: [].
"
`;

exports[`validation should fail for pluginsNotArray 1`] = `
"Commitlint configuration in pluginsNotArray.js is invalid:
- Property \\"plugins\\" has the wrong type - should be array.
"
`;

exports[`validation should fail for rules1 1`] = `
"Commitlint configuration in rules1.js is invalid:
- \\"rules['a'][0]\\" should be equal to one of the allowed values. Value: 3.
- \\"rules['a']\\" should be a function. Value: [3].
- \\"rules['a']\\" should match exactly one schema in oneOf. Value: [3].
"
`;

exports[`validation should fail for rules2 1`] = `
"Commitlint configuration in rules2.js is invalid:
- \\"rules['b']\\" should NOT have more than 3 items. Value: [1,\\"test\\",2,2].
- \\"rules['b']\\" should be a function. Value: [1,\\"test\\",2,2].
- \\"rules['b']\\" should match exactly one schema in oneOf. Value: [1,\\"test\\",2,2].
"
`;

exports[`validation should fail for rules3 1`] = `
"Commitlint configuration in rules3.js is invalid:
- \\"rules['c']\\" should NOT have fewer than 1 items. Value: [].
- \\"rules['c']\\" should be a function. Value: [].
- \\"rules['c']\\" should match exactly one schema in oneOf. Value: [].
"
`;

exports[`validation should fail for rules4 1`] = `
"Commitlint configuration in rules4.js is invalid:
- Property \\"rules['d'][0]\\" has the wrong type - should be number.
- \\"rules['d'][0]\\" should be equal to one of the allowed values. Value: [].
- \\"rules['d']\\" should be a function. Value: [[],[],[]].
- \\"rules['d']\\" should match exactly one schema in oneOf. Value: [[],[],[]].
"
`;

exports[`validation should fail for rules5 1`] = `
"Commitlint configuration in rules5.js is invalid:
- Property \\"rules['e']\\" has the wrong type - should be array.
- \\"rules['e']\\" should be a function. Value: {}.
- \\"rules['e']\\" should match exactly one schema in oneOf. Value: {}.
"
`;

exports[`validation should fail for rulesAsArray 1`] = `
"Commitlint configuration in rulesAsArray.js is invalid:
- Property \\"rules\\" has the wrong type - should be object.
"
`;

exports[`validation should fail for whenConfigIsNotObject 1`] = `
"Commitlint configuration in whenConfigIsNotObject.js is invalid:
- Config has the wrong type - should be object.
"
`;

exports[`validation should fail for whenConfigIsNotObject2 1`] = `
"Commitlint configuration in whenConfigIsNotObject2.js is invalid:
- Config has the wrong type - should be object.
"
`;

exports[`validation should fail for withPluginsAsObject 1`] = `
"Commitlint configuration in withPluginsAsObject.js is invalid:
- Property \\"plugins[0]\\" has the wrong type - should be string.
- \\"plugins[0]\\" should have required property '.rules'. Value: {}.
- \\"plugins[0]\\" should match some schema in anyOf. Value: {}.
"
`;
100 changes: 100 additions & 0 deletions @commitlint/config-validator/src/commitlint.schema.json
@@ -0,0 +1,100 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"default": {},
"type": "object",
"definitions": {
"rule": {
"oneOf": [
{
"description": "A rule",
"type": "array",
"items": [
{
"description": "Level: 0 disables the rule. For 1 it will be considered a warning, for 2 an error",
"type": "number",
"enum": [0, 1, 2]
},
{
"description": "Applicable: always|never: never inverts the rule",
"type": "string",
"enum": ["always", "never"]
},
{
"description": "Value: the value for this rule"
}
],
"minItems": 1,
"maxItems": 3,
"additionalItems": false
},
{
"description": "A rule",
"typeof": "function"
}
]
}
},
"properties": {
"extends": {
"description": "Resolveable ids to commitlint configurations to extend",
"oneOf": [
{
"type": "array",
"items": {"type": "string"}
},
{"type": "string"}
]
},
"parserPreset": {
"description": "Resolveable id to conventional-changelog parser preset to import and use",
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"name": {"type": "string"},
"path": {"type": "string"},
"parserOpts": {}
},
"additionalProperties": true
}
]
},
"helpUrl": {
"description": "Custom URL to show upon failure",
"type": "string"
},
"formatter": {
"description": "Resolveable id to package, from node_modules, which formats the output",
"type": "string"
},
"rules": {
"description": "Rules to check against",
"type": "object",
"propertyNames": {"type": "string"},
"additionalProperties": {"$ref": "#/definitions/rule"}
},
"plugins": {
"description": "Resolveable ids of commitlint plugins from node_modules",
"type": "array",
"items": {
"anyOf": [
{"type": "string"},
{
"required": ["rules"],
"rules": {}
}
]
}
},
"ignores": {
"type": "array",
"items": {"typeof": "function"},
"description": "Additional commits to ignore, defined by ignore matchers"
},
"defaultIgnores": {
"description": "Whether commitlint uses the default ignore rules",
"type": "boolean"
}
}
}
45 changes: 45 additions & 0 deletions @commitlint/config-validator/src/formatErrors.ts
@@ -0,0 +1,45 @@
import {ErrorObject} from 'ajv';

/**
* Formats an array of schema validation errors.
* @param errors An array of error messages to format.
* @returns Formatted error message
* Based on https://github.com/eslint/eslint/blob/master/lib/shared/config-validator.js#L237-L261
*/
export function formatErrors(errors: ErrorObject[]): string {
return errors
.map((error) => {
if (
error.keyword === 'additionalProperties' &&
'additionalProperty' in error.params
) {
const formattedPropertyPath = error.dataPath.length
? `${error.dataPath.slice(1)}.${error.params.additionalProperty}`
: error.params.additionalProperty;

return `Unexpected top-level property "${formattedPropertyPath}"`;
}
if (error.keyword === 'type') {
const formattedField = error.dataPath.slice(1);
if (!formattedField) {
return `Config has the wrong type - ${error.message}`;
}
return `Property "${formattedField}" has the wrong type - ${error.message}`;
}
const field =
(error.dataPath[0] === '.'
? error.dataPath.slice(1)
: error.dataPath) || 'Config';
if (error.keyword === 'typeof') {
return `"${field}" should be a ${error.schema}. Value: ${JSON.stringify(
error.data
)}`;
}

return `"${field}" ${error.message}. Value: ${JSON.stringify(
error.data
)}`;
})
.map((message) => `\t- ${message}.\n`)
.join('');
}

0 comments on commit c717202

Please sign in to comment.