diff --git a/@commitlint/cli/cli.js b/@commitlint/cli/cli.js index 242a75f216..eb10ac751f 100755 --- a/@commitlint/cli/cli.js +++ b/@commitlint/cli/cli.js @@ -23,7 +23,7 @@ const rules = { }; const configuration = { - string: ['from', 'to', 'extends'], + string: ['from', 'to', 'extends', 'parserPreset'], boolean: ['edit', 'help', 'version', 'quiet', 'color'], alias: { c: 'color', @@ -33,7 +33,8 @@ const configuration = { q: 'quiet', h: 'help', v: 'version', - x: 'extends' + x: 'extends', + p: 'parserPreset' }, description: { color: 'toggle colored output', @@ -41,7 +42,8 @@ const configuration = { extends: 'array of shareable configurations to extend', from: 'lower end of the commit range to lint; applies if edit=false', to: 'upper end of the commit range to lint; applies if edit=false', - quiet: 'toggle console output' + quiet: 'toggle console output', + parserPreset: 'preset parser' }, default: { color: true, @@ -80,7 +82,7 @@ function main(options) { Promise.all( messages.map(commit => { return load(getSeed(flags)) - .then(opts => core.lint(commit, opts.rules)) + .then(opts => core.lint(commit, opts.rules, opts)) .then(report => { const formatted = core.format(report, {color: flags.color}); @@ -106,7 +108,9 @@ function main(options) { function getSeed(seed) { const e = Array.isArray(seed.extends) ? seed.extends : [seed.extends]; const n = e.filter(i => typeof i === 'string'); - return n.length > 0 ? {extends: n} : {}; + return n.length > 0 + ? {extends: n, parserPreset: seed.parserPreset} + : {parserPreset: seed.parserPreset}; } // Start the engine diff --git a/@commitlint/core/fixtures/parser-preset/commitlint.config.js b/@commitlint/core/fixtures/parser-preset/commitlint.config.js new file mode 100644 index 0000000000..f2b73fa44e --- /dev/null +++ b/@commitlint/core/fixtures/parser-preset/commitlint.config.js @@ -0,0 +1,3 @@ +module.exports = { + parserPreset: './conventional-changelog-custom' +}; diff --git a/@commitlint/core/fixtures/parser-preset/conventional-changelog-custom.js b/@commitlint/core/fixtures/parser-preset/conventional-changelog-custom.js new file mode 100644 index 0000000000..434b48ce13 --- /dev/null +++ b/@commitlint/core/fixtures/parser-preset/conventional-changelog-custom.js @@ -0,0 +1,8 @@ +const defaultOpts = require('conventional-changelog-angular'); +const _ = require('lodash'); + +module.exports = defaultOpts.then(data => { + const extented = _.cloneDeep(data); + extented.parserOpts.headerPattern = /^(\w*)(?:\((.*)\))?-(.*)$/; + return extented; +}); diff --git a/@commitlint/core/fixtures/recursive-parser-preset/commitlint.config.js b/@commitlint/core/fixtures/recursive-parser-preset/commitlint.config.js new file mode 100644 index 0000000000..62804e2b03 --- /dev/null +++ b/@commitlint/core/fixtures/recursive-parser-preset/commitlint.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['./first-extended'] +}; diff --git a/@commitlint/core/fixtures/recursive-parser-preset/first-extended/index.js b/@commitlint/core/fixtures/recursive-parser-preset/first-extended/index.js new file mode 100644 index 0000000000..6ec50f348e --- /dev/null +++ b/@commitlint/core/fixtures/recursive-parser-preset/first-extended/index.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['./second-extended'] +}; diff --git a/@commitlint/core/fixtures/recursive-parser-preset/first-extended/second-extended/conventional-changelog-custom.js b/@commitlint/core/fixtures/recursive-parser-preset/first-extended/second-extended/conventional-changelog-custom.js new file mode 100644 index 0000000000..434b48ce13 --- /dev/null +++ b/@commitlint/core/fixtures/recursive-parser-preset/first-extended/second-extended/conventional-changelog-custom.js @@ -0,0 +1,8 @@ +const defaultOpts = require('conventional-changelog-angular'); +const _ = require('lodash'); + +module.exports = defaultOpts.then(data => { + const extented = _.cloneDeep(data); + extented.parserOpts.headerPattern = /^(\w*)(?:\((.*)\))?-(.*)$/; + return extented; +}); diff --git a/@commitlint/core/fixtures/recursive-parser-preset/first-extended/second-extended/index.js b/@commitlint/core/fixtures/recursive-parser-preset/first-extended/second-extended/index.js new file mode 100644 index 0000000000..f2b73fa44e --- /dev/null +++ b/@commitlint/core/fixtures/recursive-parser-preset/first-extended/second-extended/index.js @@ -0,0 +1,3 @@ +module.exports = { + parserPreset: './conventional-changelog-custom' +}; diff --git a/@commitlint/core/src/library/parse.js b/@commitlint/core/src/library/parse.js index c86262b582..fc0e5b16a6 100644 --- a/@commitlint/core/src/library/parse.js +++ b/@commitlint/core/src/library/parse.js @@ -1,16 +1,14 @@ import {sync} from 'conventional-commits-parser'; +import defaultChangelogOpts from 'conventional-changelog-angular'; export default parse; -async function parse(message, parser = sync) { - // Prevent conventional-changelog-angular from spamming startup - // TODO: Remove when https://github.com/conventional-changelog/conventional-changelog/pull/206 lands - const _error = console.error; - console.error = () => {}; - const opts = require('conventional-changelog-angular'); - console.error = _error; +async function parse(message, parser = sync, parserOpts) { + if (!parserOpts) { + const changelogOpts = await defaultChangelogOpts; + parserOpts = changelogOpts.parserOpts; + } - const {parserOpts} = await opts; const parsed = parser(message, parserOpts); parsed.raw = message; return parsed; diff --git a/@commitlint/core/src/library/parse.test.js b/@commitlint/core/src/library/parse.test.js index 03382ce84d..fe3a8c7f56 100644 --- a/@commitlint/core/src/library/parse.test.js +++ b/@commitlint/core/src/library/parse.test.js @@ -1,3 +1,4 @@ +import importFrom from 'import-from'; import test from 'ava'; import parse from './parse'; @@ -71,6 +72,30 @@ test('uses angular grammar', async t => { t.deepEqual(actual, expected); }); +test('uses custom opts parser', async t => { + const message = 'type(scope)-subject'; + const changelogOpts = await importFrom( + process.cwd(), + './fixtures/parser-preset/conventional-changelog-custom' + ); + const actual = await parse(message, undefined, changelogOpts.parserOpts); + const expected = { + body: null, + footer: null, + header: 'type(scope)-subject', + mentions: [], + merge: null, + notes: [], + raw: 'type(scope)-subject', + references: [], + revert: null, + scope: 'scope', + subject: 'subject', + type: 'type' + }; + t.deepEqual(actual, expected); +}); + test('supports scopes with /', async t => { const message = 'type(some/scope): subject'; const actual = await parse(message); diff --git a/@commitlint/core/src/library/resolve-extends.js b/@commitlint/core/src/library/resolve-extends.js index bc041e9bfb..33674465d3 100644 --- a/@commitlint/core/src/library/resolve-extends.js +++ b/@commitlint/core/src/library/resolve-extends.js @@ -38,6 +38,10 @@ function loadExtends(config = {}, context = {}) { cwd: path.dirname(resolved) }); + if (c && c.parserPreset) { + c.parserPreset = resolveId(c.parserPreset, ctx); + } + return [...configs, c, ...loadExtends(c, ctx)]; }, []); } diff --git a/@commitlint/core/src/lint.js b/@commitlint/core/src/lint.js index d3dff6d84e..3612ff02eb 100644 --- a/@commitlint/core/src/lint.js +++ b/@commitlint/core/src/lint.js @@ -3,7 +3,7 @@ import isIgnored from './library/is-ignored'; import parse from './library/parse'; import implementations from './rules'; -export default async (message, rules = {}) => { +export default async (message, rules = {}, opts = {}) => { // Found a wildcard match, skip if (isIgnored(message)) { return { @@ -14,7 +14,7 @@ export default async (message, rules = {}) => { } // Parse the commit message - const parsed = await parse(message); + const parsed = await parse(message, undefined, opts.parserOpts); // Validate against all rules const results = entries(rules) diff --git a/@commitlint/core/src/lint.test.js b/@commitlint/core/src/lint.test.js index 750e8cf739..0864176bf4 100644 --- a/@commitlint/core/src/lint.test.js +++ b/@commitlint/core/src/lint.test.js @@ -34,3 +34,19 @@ test('positive on ignored message and broken rule', async t => { }); t.true(actual.valid); }); + +test('positive on stub message and opts', async t => { + const actual = await lint( + 'foo-bar', + { + 'type-enum': [2, 'always', ['foo']], + 'type-empty': [2, 'never'] + }, + { + parserOpts: { + headerPattern: /^(\w*)(?:\((.*)\))?-(.*)$/ + } + } + ); + t.true(actual.valid); +}); diff --git a/@commitlint/core/src/load.js b/@commitlint/core/src/load.js index ba17a91c84..67c080ee8a 100644 --- a/@commitlint/core/src/load.js +++ b/@commitlint/core/src/load.js @@ -8,7 +8,7 @@ import resolveExtends from './library/resolve-extends'; import executeRule from './library/execute-rule'; const w = (a, b) => (Array.isArray(b) ? b : undefined); -const valid = input => pick(input, 'extends', 'rules'); +const valid = input => pick(input, 'extends', 'rules', 'parserPreset'); export default async (seed = {}) => { // Obtain config from .rc files @@ -24,7 +24,11 @@ export default async (seed = {}) => { cwd: raw.config ? path.dirname(raw.config) : process.cwd() }); - const preset = valid(mergeWith({}, extended, config, w)); + const preset = valid(mergeWith(extended, config, w)); + + if (preset.parserPreset) { + preset.parserOpts = await importFrom(process.cwd(), preset.parserPreset); + } // Execute rule config functions if needed const executed = await Promise.all( diff --git a/@commitlint/core/src/load.test.js b/@commitlint/core/src/load.test.js index 5b955a99f3..9fcd07711e 100644 --- a/@commitlint/core/src/load.test.js +++ b/@commitlint/core/src/load.test.js @@ -21,6 +21,12 @@ test('uses seed as configured', async t => { t.is(actual.rules.foo, 'bar'); }); +test('uses seed with parserPreset', async t => { + t.context.back = chdir('fixtures/parser-preset'); + const actual = await load({parserPreset: './conventional-changelog-custom'}); + t.truthy(actual.parserOpts); +}); + test('invalid extend should throw', t => { t.context.back = chdir('fixtures/extends-invalid'); t.throws(load()); @@ -51,6 +57,12 @@ test('recursive extends', async t => { }); }); +test('recursive extends with parserPreset', async t => { + t.context.back = chdir('fixtures/recursive-parser-preset'); + const actual = await load(); + t.truthy(actual.parserOpts); +}); + test('ignores unknow keys', async t => { t.context.back = chdir('fixtures/trash-file'); const actual = await load(); diff --git a/appveyor.yml b/appveyor.yml index bd30a3066f..c2310c1005 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,9 @@ environment: matrix: - - nodejs_version: '4' + - nodejs_version: '6' install: - ps: Install-Product node $env:nodejs_version - set CI=true - - npm install -g npm@4 - set PATH=%APPDATA%\npm;%PATH% - npm install matrix: