Skip to content

Commit 786e400

Browse files
committed
fix: simplify config resolution and fix issue conventional-changelog#327
1 parent 3f6d95c commit 786e400

File tree

4 files changed

+104
-37
lines changed

4 files changed

+104
-37
lines changed

@commitlint/load/src/load.ts

+7-17
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Path from 'path';
22

33
import merge from 'lodash/merge';
4-
import union from 'lodash/union';
4+
import uniq from 'lodash/uniq';
55
import resolveFrom from 'resolve-from';
66

77
import executeRule from '@commitlint/execute-rule';
@@ -35,6 +35,8 @@ export default async function load(
3535
const config = pickConfig(
3636
merge(
3737
{
38+
extends: [],
39+
plugins: [],
3840
rules: {},
3941
formatter: '@commitlint/format',
4042
helpUrl:
@@ -57,7 +59,7 @@ export default async function load(
5759
}
5860

5961
// Resolve extends key
60-
const extended = resolveExtends(config, {
62+
const extended = resolveExtends(config as any, {
6163
prefix: 'commitlint-config',
6264
cwd: base,
6365
parserPreset: config.parserPreset,
@@ -66,13 +68,7 @@ export default async function load(
6668
validateConfig(extended);
6769

6870
let plugins: PluginRecords = {};
69-
// TODO: this object merging should be done in resolveExtends
70-
union(
71-
// Read plugins from config
72-
Array.isArray(config.plugins) ? config.plugins : [],
73-
// Read plugins from extends
74-
Array.isArray(extended.plugins) ? extended.plugins : []
75-
).forEach((plugin) => {
71+
uniq(extended.plugins || []).forEach((plugin) => {
7672
if (typeof plugin === 'string') {
7773
plugins = loadPlugin(plugins, plugin, process.env.DEBUG === 'true');
7874
} else {
@@ -82,10 +78,7 @@ export default async function load(
8278

8379
const rules = (
8480
await Promise.all(
85-
Object.entries({
86-
...(typeof extended.rules === 'object' ? extended.rules || {} : {}),
87-
...(typeof config.rules === 'object' ? config.rules || {} : {}),
88-
}).map((entry) => executeRule(entry))
81+
Object.entries(extended.rules || {}).map((entry) => executeRule(entry))
8982
)
9083
).reduce<QualifiedRules>((registry, item) => {
9184
// type of `item` can be null, but Object.entries always returns key pair
@@ -111,9 +104,6 @@ export default async function load(
111104
defaultIgnores: extended.defaultIgnores,
112105
plugins: plugins,
113106
rules: rules,
114-
helpUrl:
115-
typeof extended.helpUrl === 'string'
116-
? extended.helpUrl
117-
: 'https://github.com/conventional-changelog/commitlint/#what-is-commitlint',
107+
helpUrl: extended.helpUrl,
118108
};
119109
}

@commitlint/load/src/utils/validators.ts

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import {Plugin, RulesConfig} from '@commitlint/types';
2+
13
export function isObjectLike(obj: unknown): obj is Record<string, unknown> {
24
return Boolean(obj) && typeof obj === 'object'; // typeof null === 'object'
35
}
@@ -25,22 +27,33 @@ export function validateConfig(
2527
formatter: string;
2628
ignores?: ((commit: string) => boolean)[];
2729
defaultIgnores?: boolean;
30+
plugins?: (Plugin | string)[];
31+
rules: Partial<RulesConfig>;
32+
helpUrl: string;
2833
[key: string]: unknown;
2934
} {
3035
if (!isObjectLike(config)) {
31-
throw new Error('Invalid configuration, parserPreset must be an object');
36+
throw new Error('Invalid configuration, `parserPreset` must be an object');
3237
}
3338
if (typeof config.formatter !== 'string') {
34-
throw new Error('Invalid configuration, formatter must be a string');
39+
throw new Error('Invalid configuration, `formatter` must be a string');
3540
}
3641
if (config.ignores && !Array.isArray(config.ignores)) {
37-
throw new Error('Invalid configuration, ignores must ba an array');
42+
throw new Error('Invalid configuration, `ignores` must ba an array');
43+
}
44+
if (config.plugins && !Array.isArray(config.plugins)) {
45+
throw new Error('Invalid configuration, `plugins` must ba an array');
3846
}
3947
if (
4048
typeof config.defaultIgnores !== 'boolean' &&
4149
typeof config.defaultIgnores !== 'undefined'
4250
) {
43-
throw new Error('Invalid configuration, defaultIgnores must ba true/false');
51+
throw new Error(
52+
'Invalid configuration, `defaultIgnores` must ba true/false'
53+
);
54+
}
55+
if (typeof config.helpUrl !== 'string') {
56+
throw new Error('Invalid configuration, `helpUrl` must be a string');
4457
}
4558
}
4659

@commitlint/resolve-extends/src/index.test.ts

+47
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,50 @@ test('should fall back to conventional-changelog-lint-config prefix', () => {
348348
},
349349
});
350350
});
351+
352+
// https://github.com/conventional-changelog/commitlint/issues/327
353+
test('parserPreset should resolve correctly in extended configuration', () => {
354+
const input = {extends: ['extender-name'], zero: 'root'};
355+
356+
const require = (id: string) => {
357+
switch (id) {
358+
case 'extender-name':
359+
return {
360+
extends: ['recursive-extender-name'],
361+
parserPreset: {
362+
parserOpts: {
363+
issuePrefixes: ['#', '!', '&', 'no-references'],
364+
referenceActions: null,
365+
},
366+
},
367+
};
368+
case 'recursive-extender-name':
369+
return {
370+
parserPreset: {
371+
parserOpts: {
372+
issuePrefixes: ['#', '!'],
373+
},
374+
},
375+
};
376+
default:
377+
return {};
378+
}
379+
};
380+
381+
const ctx = {resolve: id, require: jest.fn(require)} as ResolveExtendsContext;
382+
383+
const actual = resolveExtends(input, ctx);
384+
385+
const expected = {
386+
extends: ['extender-name'],
387+
parserPreset: {
388+
parserOpts: {
389+
issuePrefixes: ['#', '!', '&', 'no-references'],
390+
referenceActions: null,
391+
},
392+
},
393+
zero: 'root',
394+
};
395+
396+
expect(actual).toEqual(expected);
397+
});

@commitlint/resolve-extends/src/index.ts

+33-16
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import 'resolve-global';
44
import resolveFrom from 'resolve-from';
55
import merge from 'lodash/merge';
66
import mergeWith from 'lodash/mergeWith';
7-
import {UserConfig} from '@commitlint/types';
87

98
const importFresh = require('import-fresh');
109

10+
export interface ResolveExtendsConfig {
11+
extends?: string | string[];
12+
[key: string]: unknown;
13+
}
14+
1115
export interface ResolvedConfig {
1216
parserPreset?: unknown;
1317
[key: string]: unknown;
@@ -22,32 +26,45 @@ export interface ResolveExtendsContext {
2226
require?<T>(id: string): T;
2327
}
2428

29+
function mergeStrategy(objValue: unknown, srcValue: unknown, key: string) {
30+
if (key === 'parserPreset') {
31+
if (typeof srcValue !== 'object') {
32+
return objValue;
33+
}
34+
} else if (key === 'rules') {
35+
if (typeof objValue !== 'object') {
36+
return srcValue;
37+
}
38+
} else if (key === 'plugins') {
39+
if (!Array.isArray(objValue)) {
40+
return srcValue;
41+
}
42+
} else if (Array.isArray(objValue)) {
43+
return srcValue;
44+
}
45+
}
46+
2547
export default function resolveExtends(
26-
config: UserConfig = {},
48+
config: ResolveExtendsConfig = {},
2749
context: ResolveExtendsContext = {}
28-
) {
50+
): ResolvedConfig {
2951
const {extends: e} = config;
30-
const extended = loadExtends(config, context).reduce(
31-
(r, {extends: _, ...c}) =>
32-
mergeWith(r, c, (objValue, srcValue) => {
33-
if (Array.isArray(objValue)) {
34-
return srcValue;
35-
}
36-
}),
52+
const extended = loadExtends(config, context);
53+
extended.push(config);
54+
return extended.reduce(
55+
(r, {extends: _, ...c}) => mergeWith(r, c, mergeStrategy),
3756
e ? {extends: e} : {}
3857
);
39-
40-
return merge({}, extended, config);
4158
}
4259

4360
function loadExtends(
44-
config: UserConfig = {},
61+
config: ResolveExtendsConfig = {},
4562
context: ResolveExtendsContext = {}
4663
): ResolvedConfig[] {
4764
const {extends: e} = config;
48-
const ext = e ? (Array.isArray(e) ? e : [e]) : [];
65+
const ext = e ? (Array.isArray(e) ? [...e] : [e]) : [];
4966

50-
return ext.reduce<ResolvedConfig[]>((configs, raw) => {
67+
return ext.reverse().reduce<ResolvedConfig[]>((configs, raw) => {
5168
const load = context.require || require;
5269
const resolved = resolveConfig(raw, context);
5370
const c = load(resolved);
@@ -73,7 +90,7 @@ function loadExtends(
7390
config.parserPreset = parserPreset;
7491
}
7592

76-
return [...configs, ...loadExtends(c, ctx), c];
93+
return [...loadExtends(c, ctx), c, ...configs];
7794
}, []);
7895
}
7996

0 commit comments

Comments
 (0)