Skip to content

Commit 70be7d6

Browse files
committed
feat: implement config validation
1 parent ee0650d commit 70be7d6

File tree

42 files changed

+572
-178
lines changed

Some content is hidden

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

42 files changed

+572
-178
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "@commitlint/config-validator",
3+
"version": "11.0.0",
4+
"description": "commitizen prompt using commitlint.config.js",
5+
"main": "./lib/validate.js",
6+
"files": [
7+
"lib/"
8+
],
9+
"scripts": {
10+
"deps": "dep-check",
11+
"pkg": "pkg-check --skip-import"
12+
},
13+
"repository": {
14+
"type": "git",
15+
"url": "https://github.com/conventional-changelog/commitlint.git"
16+
},
17+
"author": "Mario Nebl <[email protected]>",
18+
"license": "MIT",
19+
"bugs": {
20+
"url": "https://github.com/conventional-changelog/commitlint/issues"
21+
},
22+
"homepage": "https://github.com/conventional-changelog/commitlint#readme",
23+
"engines": {
24+
"node": ">=v10"
25+
},
26+
"devDependencies": {
27+
"@commitlint/utils": "^11.0.0"
28+
},
29+
"dependencies": {
30+
"@commitlint/types": "^11.0.0",
31+
"ajv": "^6.12.6"
32+
},
33+
"gitHead": "cb565dfcca3128380b9b3dc274aedbcae34ce5ca"
34+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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 match exactly one schema in oneOf. Value: [3].
73+
"
74+
`;
75+
76+
exports[`validation should fail for rules2 1`] = `
77+
"Commitlint configuration in rules2.js is invalid:
78+
- \\"rules['b']\\" should NOT have more than 3 items. Value: [1,\\"test\\",2,2].
79+
- \\"rules['b']\\" should match exactly one schema in oneOf. Value: [1,\\"test\\",2,2].
80+
"
81+
`;
82+
83+
exports[`validation should fail for rules3 1`] = `
84+
"Commitlint configuration in rules3.js is invalid:
85+
- \\"rules['c']\\" should NOT have fewer than 1 items. Value: [].
86+
- \\"rules['c']\\" should match exactly one schema in oneOf. Value: [].
87+
"
88+
`;
89+
90+
exports[`validation should fail for rules4 1`] = `
91+
"Commitlint configuration in rules4.js is invalid:
92+
- Property \\"rules['d'][0]\\" has the wrong type - should be number.
93+
- \\"rules['d'][0]\\" should be equal to one of the allowed values. Value: [].
94+
- \\"rules['d']\\" should match exactly one schema in oneOf. Value: [[],[],[]].
95+
"
96+
`;
97+
98+
exports[`validation should fail for rules5 1`] = `
99+
"Commitlint configuration in rules5.js is invalid:
100+
- Property \\"rules['e']\\" has the wrong type - should be array.
101+
- \\"rules['e']\\" should match exactly one schema in oneOf. Value: {}.
102+
"
103+
`;
104+
105+
exports[`validation should fail for rulesAsArray 1`] = `
106+
"Commitlint configuration in rulesAsArray.js is invalid:
107+
- Property \\"rules\\" has the wrong type - should be object.
108+
"
109+
`;
110+
111+
exports[`validation should fail for whenConfigIsNotObject 1`] = `
112+
"Commitlint configuration in whenConfigIsNotObject.js is invalid:
113+
- Config has the wrong type - should be object.
114+
"
115+
`;
116+
117+
exports[`validation should fail for whenConfigIsNotObject2 1`] = `
118+
"Commitlint configuration in whenConfigIsNotObject2.js is invalid:
119+
- Config has the wrong type - should be object.
120+
"
121+
`;
122+
123+
exports[`validation should fail for withPluginsAsObject 1`] = `
124+
"Commitlint configuration in withPluginsAsObject.js is invalid:
125+
- Property \\"plugins[0]\\" has the wrong type - should be string.
126+
- \\"plugins[0]\\" should have required property '.rules'. Value: {}.
127+
- \\"plugins[0]\\" should match some schema in anyOf. Value: {}.
128+
"
129+
`;
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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+
}
32+
},
33+
"properties": {
34+
"extends": {
35+
"description": "Resolveable ids to commitlint configurations to extend",
36+
"oneOf": [
37+
{
38+
"type": "array",
39+
"items": {"type": "string"}
40+
},
41+
{"type": "string"}
42+
]
43+
},
44+
"parserPreset": {
45+
"description": "Resolveable id to conventional-changelog parser preset to import and use",
46+
"oneOf": [
47+
{"type": "string"},
48+
{
49+
"type": "object",
50+
"properties": {
51+
"name": {"type": "string"},
52+
"path": {"type": "string"},
53+
"parserOpts": {}
54+
},
55+
"additionalProperties": true
56+
}
57+
]
58+
},
59+
"helpUrl": {
60+
"description": "Custom URL to show upon failure",
61+
"type": "string"
62+
},
63+
"formatter": {
64+
"description": "Resolveable id to package, from node_modules, which formats the output",
65+
"type": "string"
66+
},
67+
"rules": {
68+
"description": "Rules to check against",
69+
"type": "object",
70+
"propertyNames": {"type": "string"},
71+
"additionalProperties": {"$ref": "#/definitions/rule"}
72+
},
73+
"plugins": {
74+
"description": "Resolveable ids of commitlint plugins from node_modules",
75+
"type": "array",
76+
"items": {
77+
"anyOf": [
78+
{"type": "string"},
79+
{
80+
"required": ["rules"],
81+
"rules": {}
82+
}
83+
]
84+
}
85+
},
86+
"ignores": {
87+
"type": "array",
88+
"items": {"typeof": "function"},
89+
"description": "Additional commits to ignore, defined by ignore matchers"
90+
},
91+
"defaultIgnores": {
92+
"description": "Whether commitlint uses the default ignore rules",
93+
"type": "boolean"
94+
}
95+
}
96+
}
Lines changed: 45 additions & 0 deletions
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+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {validateConfig} from './validate';
2+
import {UserConfig} from '@commitlint/types';
3+
4+
const validSchemas: Record<string, UserConfig> = {
5+
empty: {},
6+
withEmptyExtends: {extends: []},
7+
withStringExtends: {extends: 'test'},
8+
withSingleExtends: {extends: ['test']},
9+
withMultipleExtends: {extends: ['test', 'test2']},
10+
withFormatter: {formatter: ''},
11+
withHelpUrl: {helpUrl: ''},
12+
withRules: {rules: {a: [0], b: [1, 'never'], c: [2, 'never', true]}},
13+
withParserPresetString: {parserPreset: 'test'},
14+
withParserPresetObject: {parserPreset: {}},
15+
withParserPresetObject2: {parserPreset: {name: 'string', path: 'string'}},
16+
withParserPresetObjectPromise: {
17+
parserPreset: Promise.resolve({name: 'string'}),
18+
},
19+
withParserPresetOpts: {parserPreset: {parserOpts: {test: 1}}},
20+
withParserPresetOptsPromise: {
21+
parserPreset: {parserOpts: Promise.resolve({test: 1})},
22+
},
23+
withEmptyIgnores: {ignores: []},
24+
withIgnores: {ignores: [() => true]},
25+
withDefaultIgnoresTrue: {defaultIgnores: true},
26+
withDefaultIgnoresFalse: {defaultIgnores: false},
27+
withEmptyPlugins: {plugins: []},
28+
withPluginsAsString: {plugins: ['test']},
29+
withPluginsAsObject: {plugins: [{rules: {}}]},
30+
shouldSkipAllowAdditionalProperties: {foo: 1},
31+
};
32+
33+
const invalidSchemas: Record<string, any> = {
34+
whenConfigIsNotObject: [],
35+
whenConfigIsNotObject2: '',
36+
extendsAsObject: {extends: {test: 1}},
37+
extendsWithFunction: {extends: [() => true]},
38+
formatterAsObject: {formatter: {}},
39+
helpUrlAsArray: {helpUrl: []},
40+
rulesAsArray: {rules: ['a']},
41+
rules1: {rules: {a: [3]}},
42+
rules2: {rules: {b: [1, 'test', 2, 2]}},
43+
rules3: {rules: {c: []}},
44+
rules4: {rules: {d: [[], [], []]}},
45+
rules5: {rules: {e: {}}},
46+
parserPreset: {parserPreset: []},
47+
ignoresFunction: {ignores: () => true},
48+
ignoresNotFunction: {ignores: [1]},
49+
defaultIgnoresNotBoolean: {defaultIgnores: 'true'},
50+
pluginsNotArray: {plugins: 'test'},
51+
withPluginsAsObject: {plugins: [{}]},
52+
helpUrlNotString: {helpUrl: {}},
53+
};
54+
55+
describe('validation should pass for', () => {
56+
test.each(Object.entries(validSchemas))('%s', (file, config) => {
57+
expect(() => validateConfig(`${file}.js`, config)).not.toThrowError();
58+
});
59+
});
60+
61+
describe('validation should fail for', () => {
62+
test.each(Object.entries(invalidSchemas))('%s', (file, config) => {
63+
expect(() =>
64+
validateConfig(`${file}.js`, config)
65+
).toThrowErrorMatchingSnapshot();
66+
});
67+
});

0 commit comments

Comments
 (0)