Skip to content

Commit c717202

Browse files
authored
feat: config validation (#2412)
* 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
1 parent fc5d869 commit c717202

File tree

45 files changed

+693
-2024
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+693
-2024
lines changed

@commitlint/cli/src/cli.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import resolveGlobal from 'resolve-global';
77
import yargs, {Arguments} from 'yargs';
88
import util from 'util';
99

10-
import {CliFlags, Seed} from './types';
10+
import {CliFlags} from './types';
1111
import {
1212
LintOptions,
1313
LintOutcome,
1414
ParserOptions,
1515
ParserPreset,
1616
QualifiedConfig,
1717
Formatter,
18+
UserConfig,
1819
} from '@commitlint/types';
1920
import {CliError} from './cli-error';
2021

@@ -364,7 +365,7 @@ function getEditValue(flags: CliFlags) {
364365
return edit;
365366
}
366367

367-
function getSeed(flags: CliFlags): Seed {
368+
function getSeed(flags: CliFlags): UserConfig {
368369
const n = (flags.extends || []).filter(
369370
(i): i is string => typeof i === 'string'
370371
);

@commitlint/cli/src/types.ts

-5
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,3 @@ export interface CliFlags {
1818
_: (string | number)[];
1919
$0: string;
2020
}
21-
22-
export interface Seed {
23-
extends?: string[];
24-
parserPreset?: string;
25-
}
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"name": "@commitlint/config-validator",
3+
"version": "15.0.0",
4+
"description": "config validator for commitlint.config.js",
5+
"main": "lib/validate.js",
6+
"types": "lib/validate.d.ts",
7+
"files": [
8+
"lib/"
9+
],
10+
"scripts": {
11+
"deps": "dep-check",
12+
"pkg": "pkg-check --skip-import"
13+
},
14+
"engines": {
15+
"node": ">=v12"
16+
},
17+
"repository": {
18+
"type": "git",
19+
"url": "https://github.com/conventional-changelog/commitlint.git",
20+
"directory": "@commitlint/config-validator"
21+
},
22+
"bugs": {
23+
"url": "https://github.com/conventional-changelog/commitlint/issues"
24+
},
25+
"homepage": "https://commitlint.js.org/",
26+
"keywords": [
27+
"conventional-changelog",
28+
"commitlint",
29+
"library",
30+
"core"
31+
],
32+
"author": {
33+
"name": "Mario Nebl",
34+
"email": "[email protected]"
35+
},
36+
"license": "MIT",
37+
"devDependencies": {
38+
"@commitlint/utils": "^15.0.0"
39+
},
40+
"dependencies": {
41+
"@commitlint/types": "^15.0.0",
42+
"ajv": "^6.12.6"
43+
},
44+
"gitHead": "d829bf6260304ca8d6811f329fcdd1b6c50e9749"
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`validation should fail for defaultIgnoresNotBoolean 1`] = `
4+
"Commitlint configuration in defaultIgnoresNotBoolean.js is invalid:
5+
- Property \\"defaultIgnores\\" has the wrong type - should be boolean.
6+
"
7+
`;
8+
9+
exports[`validation should fail for extendsAsObject 1`] = `
10+
"Commitlint configuration in extendsAsObject.js is invalid:
11+
- Property \\"extends\\" has the wrong type - should be array.
12+
- Property \\"extends\\" has the wrong type - should be string.
13+
- \\"extends\\" should match exactly one schema in oneOf. Value: {\\"test\\":1}.
14+
"
15+
`;
16+
17+
exports[`validation should fail for extendsWithFunction 1`] = `
18+
"Commitlint configuration in extendsWithFunction.js is invalid:
19+
- Property \\"extends[0]\\" has the wrong type - should be string.
20+
- Property \\"extends\\" has the wrong type - should be string.
21+
- \\"extends\\" should match exactly one schema in oneOf. Value: [null].
22+
"
23+
`;
24+
25+
exports[`validation should fail for formatterAsObject 1`] = `
26+
"Commitlint configuration in formatterAsObject.js is invalid:
27+
- Property \\"formatter\\" has the wrong type - should be string.
28+
"
29+
`;
30+
31+
exports[`validation should fail for helpUrlAsArray 1`] = `
32+
"Commitlint configuration in helpUrlAsArray.js is invalid:
33+
- Property \\"helpUrl\\" has the wrong type - should be string.
34+
"
35+
`;
36+
37+
exports[`validation should fail for helpUrlNotString 1`] = `
38+
"Commitlint configuration in helpUrlNotString.js is invalid:
39+
- Property \\"helpUrl\\" has the wrong type - should be string.
40+
"
41+
`;
42+
43+
exports[`validation should fail for ignoresFunction 1`] = `
44+
"Commitlint configuration in ignoresFunction.js is invalid:
45+
- Property \\"ignores\\" has the wrong type - should be array.
46+
"
47+
`;
48+
49+
exports[`validation should fail for ignoresNotFunction 1`] = `
50+
"Commitlint configuration in ignoresNotFunction.js is invalid:
51+
- \\"ignores[0]\\" should be a function. Value: 1.
52+
"
53+
`;
54+
55+
exports[`validation should fail for parserPreset 1`] = `
56+
"Commitlint configuration in parserPreset.js is invalid:
57+
- Property \\"parserPreset\\" has the wrong type - should be string.
58+
- Property \\"parserPreset\\" has the wrong type - should be object.
59+
- \\"parserPreset\\" should match exactly one schema in oneOf. Value: [].
60+
"
61+
`;
62+
63+
exports[`validation should fail for pluginsNotArray 1`] = `
64+
"Commitlint configuration in pluginsNotArray.js is invalid:
65+
- Property \\"plugins\\" has the wrong type - should be array.
66+
"
67+
`;
68+
69+
exports[`validation should fail for rules1 1`] = `
70+
"Commitlint configuration in rules1.js is invalid:
71+
- \\"rules['a'][0]\\" should be equal to one of the allowed values. Value: 3.
72+
- \\"rules['a']\\" should be a function. Value: [3].
73+
- \\"rules['a']\\" should match exactly one schema in oneOf. Value: [3].
74+
"
75+
`;
76+
77+
exports[`validation should fail for rules2 1`] = `
78+
"Commitlint configuration in rules2.js is invalid:
79+
- \\"rules['b']\\" should NOT have more than 3 items. Value: [1,\\"test\\",2,2].
80+
- \\"rules['b']\\" should be a function. Value: [1,\\"test\\",2,2].
81+
- \\"rules['b']\\" should match exactly one schema in oneOf. Value: [1,\\"test\\",2,2].
82+
"
83+
`;
84+
85+
exports[`validation should fail for rules3 1`] = `
86+
"Commitlint configuration in rules3.js is invalid:
87+
- \\"rules['c']\\" should NOT have fewer than 1 items. Value: [].
88+
- \\"rules['c']\\" should be a function. Value: [].
89+
- \\"rules['c']\\" should match exactly one schema in oneOf. Value: [].
90+
"
91+
`;
92+
93+
exports[`validation should fail for rules4 1`] = `
94+
"Commitlint configuration in rules4.js is invalid:
95+
- Property \\"rules['d'][0]\\" has the wrong type - should be number.
96+
- \\"rules['d'][0]\\" should be equal to one of the allowed values. Value: [].
97+
- \\"rules['d']\\" should be a function. Value: [[],[],[]].
98+
- \\"rules['d']\\" should match exactly one schema in oneOf. Value: [[],[],[]].
99+
"
100+
`;
101+
102+
exports[`validation should fail for rules5 1`] = `
103+
"Commitlint configuration in rules5.js is invalid:
104+
- Property \\"rules['e']\\" has the wrong type - should be array.
105+
- \\"rules['e']\\" should be a function. Value: {}.
106+
- \\"rules['e']\\" should match exactly one schema in oneOf. Value: {}.
107+
"
108+
`;
109+
110+
exports[`validation should fail for rulesAsArray 1`] = `
111+
"Commitlint configuration in rulesAsArray.js is invalid:
112+
- Property \\"rules\\" has the wrong type - should be object.
113+
"
114+
`;
115+
116+
exports[`validation should fail for whenConfigIsNotObject 1`] = `
117+
"Commitlint configuration in whenConfigIsNotObject.js is invalid:
118+
- Config has the wrong type - should be object.
119+
"
120+
`;
121+
122+
exports[`validation should fail for whenConfigIsNotObject2 1`] = `
123+
"Commitlint configuration in whenConfigIsNotObject2.js is invalid:
124+
- Config has the wrong type - should be object.
125+
"
126+
`;
127+
128+
exports[`validation should fail for withPluginsAsObject 1`] = `
129+
"Commitlint configuration in withPluginsAsObject.js is invalid:
130+
- Property \\"plugins[0]\\" has the wrong type - should be string.
131+
- \\"plugins[0]\\" should have required property '.rules'. Value: {}.
132+
- \\"plugins[0]\\" should match some schema in anyOf. Value: {}.
133+
"
134+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"default": {},
4+
"type": "object",
5+
"definitions": {
6+
"rule": {
7+
"oneOf": [
8+
{
9+
"description": "A rule",
10+
"type": "array",
11+
"items": [
12+
{
13+
"description": "Level: 0 disables the rule. For 1 it will be considered a warning, for 2 an error",
14+
"type": "number",
15+
"enum": [0, 1, 2]
16+
},
17+
{
18+
"description": "Applicable: always|never: never inverts the rule",
19+
"type": "string",
20+
"enum": ["always", "never"]
21+
},
22+
{
23+
"description": "Value: the value for this rule"
24+
}
25+
],
26+
"minItems": 1,
27+
"maxItems": 3,
28+
"additionalItems": false
29+
},
30+
{
31+
"description": "A rule",
32+
"typeof": "function"
33+
}
34+
]
35+
}
36+
},
37+
"properties": {
38+
"extends": {
39+
"description": "Resolveable ids to commitlint configurations to extend",
40+
"oneOf": [
41+
{
42+
"type": "array",
43+
"items": {"type": "string"}
44+
},
45+
{"type": "string"}
46+
]
47+
},
48+
"parserPreset": {
49+
"description": "Resolveable id to conventional-changelog parser preset to import and use",
50+
"oneOf": [
51+
{"type": "string"},
52+
{
53+
"type": "object",
54+
"properties": {
55+
"name": {"type": "string"},
56+
"path": {"type": "string"},
57+
"parserOpts": {}
58+
},
59+
"additionalProperties": true
60+
}
61+
]
62+
},
63+
"helpUrl": {
64+
"description": "Custom URL to show upon failure",
65+
"type": "string"
66+
},
67+
"formatter": {
68+
"description": "Resolveable id to package, from node_modules, which formats the output",
69+
"type": "string"
70+
},
71+
"rules": {
72+
"description": "Rules to check against",
73+
"type": "object",
74+
"propertyNames": {"type": "string"},
75+
"additionalProperties": {"$ref": "#/definitions/rule"}
76+
},
77+
"plugins": {
78+
"description": "Resolveable ids of commitlint plugins from node_modules",
79+
"type": "array",
80+
"items": {
81+
"anyOf": [
82+
{"type": "string"},
83+
{
84+
"required": ["rules"],
85+
"rules": {}
86+
}
87+
]
88+
}
89+
},
90+
"ignores": {
91+
"type": "array",
92+
"items": {"typeof": "function"},
93+
"description": "Additional commits to ignore, defined by ignore matchers"
94+
},
95+
"defaultIgnores": {
96+
"description": "Whether commitlint uses the default ignore rules",
97+
"type": "boolean"
98+
}
99+
}
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {ErrorObject} from 'ajv';
2+
3+
/**
4+
* Formats an array of schema validation errors.
5+
* @param errors An array of error messages to format.
6+
* @returns Formatted error message
7+
* Based on https://github.com/eslint/eslint/blob/master/lib/shared/config-validator.js#L237-L261
8+
*/
9+
export function formatErrors(errors: ErrorObject[]): string {
10+
return errors
11+
.map((error) => {
12+
if (
13+
error.keyword === 'additionalProperties' &&
14+
'additionalProperty' in error.params
15+
) {
16+
const formattedPropertyPath = error.dataPath.length
17+
? `${error.dataPath.slice(1)}.${error.params.additionalProperty}`
18+
: error.params.additionalProperty;
19+
20+
return `Unexpected top-level property "${formattedPropertyPath}"`;
21+
}
22+
if (error.keyword === 'type') {
23+
const formattedField = error.dataPath.slice(1);
24+
if (!formattedField) {
25+
return `Config has the wrong type - ${error.message}`;
26+
}
27+
return `Property "${formattedField}" has the wrong type - ${error.message}`;
28+
}
29+
const field =
30+
(error.dataPath[0] === '.'
31+
? error.dataPath.slice(1)
32+
: error.dataPath) || 'Config';
33+
if (error.keyword === 'typeof') {
34+
return `"${field}" should be a ${error.schema}. Value: ${JSON.stringify(
35+
error.data
36+
)}`;
37+
}
38+
39+
return `"${field}" ${error.message}. Value: ${JSON.stringify(
40+
error.data
41+
)}`;
42+
})
43+
.map((message) => `\t- ${message}.\n`)
44+
.join('');
45+
}

0 commit comments

Comments
 (0)