Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: pick up parser preset correctly #213

Merged
merged 8 commits into from
Dec 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions @commitlint/cli/fixtures/issue-prefixes/commitlint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
rules: {
'references-empty': [2, 'never']
},
parserPreset: {
parserOpts: {
issuePrefixes: ['REF-']
}
}
};
4 changes: 1 addition & 3 deletions @commitlint/cli/fixtures/parser-preset/commitlint.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
module.exports = {
parserOpts: {
parserPreset: './parser-preset'
},
parserPreset: './parser-preset',
rules: {
'type-enum': [2, 'always', ['type']],
'scope-enum': [2, 'always', ['scope']],
Expand Down
24 changes: 11 additions & 13 deletions @commitlint/cli/src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,17 @@ async function main(options) {
throw err;
}

return Promise.all(
messages.map(async message => {
const loaded = await core.load(getSeed(flags), {cwd: flags.cwd});
const parserOpts = selectParserOpts(loaded.parserPreset);
const opts = parserOpts ? {parserOpts} : {parserOpts: {}};
const loaded = await core.load(getSeed(flags), {cwd: flags.cwd});
const parserOpts = selectParserOpts(loaded.parserPreset);
const opts = parserOpts ? {parserOpts} : {parserOpts: {}};

// Strip comments if reading from `.git/COMMIT_EDIT_MSG`
if (range.edit) {
opts.parserOpts.commentChar = '#';
}
// Strip comments if reading from `.git/COMMIT_EDIT_MSG`
if (range.edit) {
opts.parserOpts.commentChar = '#';
}

return Promise.all(
messages.map(async message => {
const report = await core.lint(message, loaded.rules, opts);
const formatted = core.format(report, {color: flags.color});

Expand Down Expand Up @@ -182,13 +182,11 @@ function selectParserOpts(parserPreset) {
return undefined;
}

const opts = parserPreset.opts;

if (typeof opts !== 'object') {
if (typeof parserPreset.parserOpts !== 'object') {
return undefined;
}

return opts.parserOpts;
return parserPreset.parserOpts;
}

// Catch unhandled rejections globally
Expand Down
6 changes: 6 additions & 0 deletions @commitlint/cli/src/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ test('should handle --amend with signoff', async () => {
await execa('git', ['commit', '--amend', '--no-edit'], {cwd});
});

test('should handle linting with issue prefixes', async t => {
const cwd = await git.bootstrap('fixtures/issue-prefixes');
const actual = await cli([], {cwd})('foobar REF-1');
t.is(actual.code, 0);
});

async function writePkg(payload, options) {
const pkgPath = path.join(options.cwd, 'package.json');
const pkg = JSON.parse(await sander.readFile(pkgPath));
Expand Down
80 changes: 80 additions & 0 deletions @commitlint/core/src/lint.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import util from 'util';
import isIgnored from '@commitlint/is-ignored';
import parse from '@commitlint/parse';
import implementations from '@commitlint/rules';
Expand All @@ -16,6 +17,84 @@ export default async (message, rules = {}, opts = {}) => {
// Parse the commit message
const parsed = await parse(message, undefined, opts.parserOpts);

// Find invalid rules configs
const missing = Object.keys(rules).filter(
name => typeof implementations[name] !== 'function'
);

if (missing.length > 0) {
const names = Object.keys(implementations);
throw new RangeError(
`Found missing rule names: ${missing.join(
', '
)}. Supported rule names are: ${names.join(', ')}`
);
}

const invalid = entries(rules)
.map(([name, config]) => {
if (!Array.isArray(config)) {
return new Error(
`config for rule ${name} must be array, received ${util.inspect(
config
)} of type ${typeof config}`
);
}

const [level, when] = config;

if (typeof level !== 'number' || isNaN(level)) {
return new Error(
`level for rule ${name} must be number, received ${util.inspect(
level
)} of type ${typeof level}`
);
}

if (level === 0 && config.length === 1) {
return null;
}

if (config.length !== 2 && config.length !== 3) {
return new Error(
`config for rule ${name} must be 2 or 3 items long, received ${util.inspect(
config
)} of length ${config.length}`
);
}

if (level < 0 || level > 2) {
return new RangeError(
`level for rule ${name} must be between 0 and 2, received ${util.inspect(
level
)}`
);
}

if (typeof when !== 'string') {
return new Error(
`condition for rule ${name} must be string, received ${util.inspect(
when
)} of type ${typeof when}`
);
}

if (when !== 'never' && when !== 'always') {
return new Error(
`condition for rule ${name} must be "always" or "never", received ${util.inspect(
when
)}`
);
}

return null;
})
.filter(item => item instanceof Error);

if (invalid.length > 0) {
throw new Error(invalid.map(i => i.message).join('\n'));
}

// Validate against all rules
const results = entries(rules)
.filter(entry => {
Expand All @@ -32,6 +111,7 @@ export default async (message, rules = {}, opts = {}) => {
}

const rule = implementations[name];

const [valid, message] = rule(parsed, when, value);

return {
Expand Down
130 changes: 130 additions & 0 deletions @commitlint/core/src/lint.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,133 @@ test('positive on stub message and opts', async t => {
);
t.true(actual.valid);
});

test('throws for invalid rule names', async t => {
const error = await t.throws(
lint('foo', {foo: [2, 'always'], bar: [1, 'never']})
);

t.is(error.message.indexOf('Found missing rule names: foo, bar'), 0);
});

test('throws for invalid rule config', async t => {
const error = await t.throws(
lint('type(scope): foo', {
'type-enum': 1,
'scope-enum': {0: 2, 1: 'never', 2: ['foo'], length: 3}
})
);

t.true(error.message.indexOf('type-enum must be array') > -1);
t.true(error.message.indexOf('scope-enum must be array') > -1);
});

test('allows disable shorthand', async t => {
await t.notThrows(lint('foo', {'type-enum': [0], 'scope-enum': [0]}));
});

test('throws for rule with invalid length', async t => {
const error = await t.throws(
lint('type(scope): foo', {'scope-enum': [1, 2, 3, 4]})
);

t.true(error.message.indexOf('scope-enum must be 2 or 3 items long') > -1);
});

test('throws for rule with invalid level', async t => {
const error = await t.throws(
lint('type(scope): foo', {
'type-enum': ['2', 'always'],
'header-max-length': [{}, 'always']
})
);

t.true(error.message.indexOf('rule type-enum must be number') > -1);
t.true(error.message.indexOf('rule type-enum must be number') > -1);
});

test('throws for rule with out of range level', async t => {
const error = await t.throws(
lint('type(scope): foo', {
'type-enum': [-1, 'always'],
'header-max-length': [3, 'always']
})
);

t.true(error.message.indexOf('rule type-enum must be between 0 and 2') > -1);
t.true(error.message.indexOf('rule type-enum must be between 0 and 2') > -1);
});

test('throws for rule with invalid condition', async t => {
const error = await t.throws(
lint('type(scope): foo', {
'type-enum': [1, 2],
'header-max-length': [1, {}]
})
);

t.true(error.message.indexOf('type-enum must be string') > -1);
t.true(error.message.indexOf('header-max-length must be string') > -1);
});

test('throws for rule with out of range condition', async t => {
const error = await t.throws(
lint('type(scope): foo', {
'type-enum': [1, 'foo'],
'header-max-length': [1, 'bar']
})
);

t.true(error.message.indexOf('type-enum must be "always" or "never"') > -1);
t.true(
error.message.indexOf('header-max-length must be "always" or "never"') > -1
);
});

test('succeds for issue', async t => {
const report = await lint('somehting #1', {
'references-empty': [2, 'never']
});

t.true(report.valid);
});

test('fails for issue', async t => {
const report = await lint('somehting #1', {
'references-empty': [2, 'always']
});

t.false(report.valid);
});

test('succeds for custom issue prefix', async t => {
const report = await lint(
'somehting REF-1',
{
'references-empty': [2, 'never']
},
{
parserOpts: {
issuePrefixes: ['REF-']
}
}
);

t.true(report.valid);
});

test('fails for custom issue prefix', async t => {
const report = await lint(
'somehting #1',
{
'references-empty': [2, 'never']
},
{
parserOpts: {
issuePrefixes: ['REF-']
}
}
);

t.false(report.valid);
});
9 changes: 5 additions & 4 deletions @commitlint/core/src/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default async (seed = {}, options = {cwd: process.cwd()}) => {
config.parserPreset = {
name: config.parserPreset,
path: resolvedParserPreset,
opts: require(resolvedParserPreset)
parserOpts: (await require(resolvedParserPreset)).parserOpts
};
}

Expand All @@ -38,13 +38,14 @@ export default async (seed = {}, options = {cwd: process.cwd()}) => {
});

const preset = valid(mergeWith(extended, config, w));

// Await parser-preset if applicable
if (
typeof preset.parserPreset === 'object' &&
typeof preset.parserPreset.opts === 'object'
typeof preset.parserPreset.parserOpts === 'object' &&
typeof preset.parserPreset.parserOpts.then === 'function'
) {
preset.parserPreset.opts = await preset.parserPreset.opts;
preset.parserPreset.parserOpts = (await preset.parserPreset
.parserOpts).parserOpts;
}

// Execute rule config functions if needed
Expand Down
21 changes: 6 additions & 15 deletions @commitlint/core/src/load.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,9 @@ test('uses seed with parserPreset', async t => {
},
{cwd}
);

t.is(actual.name, './conventional-changelog-custom');
t.deepEqual(actual.opts, {
parserOpts: {
headerPattern: /^(\w*)(?:\((.*)\))?-(.*)$/
}
t.deepEqual(actual.parserOpts, {
headerPattern: /^(\w*)(?:\((.*)\))?-(.*)$/
});
});

Expand Down Expand Up @@ -134,25 +131,19 @@ test('recursive extends with package.json file', async t => {
test('parser preset overwrites completely instead of merging', async t => {
const cwd = await git.bootstrap('fixtures/parser-preset-override');
const actual = await load({}, {cwd});

t.is(actual.parserPreset.name, './custom');
t.is(typeof actual.parserPreset.opts, 'object');
t.deepEqual(actual.parserPreset.opts, {
b: 'b',
parserOpts: {
headerPattern: /.*/
}
t.deepEqual(actual.parserPreset.parserOpts, {
headerPattern: /.*/
});
});

test('recursive extends with parserPreset', async t => {
const cwd = await git.bootstrap('fixtures/recursive-parser-preset');
const actual = await load({}, {cwd});

t.is(actual.parserPreset.name, './conventional-changelog-custom');
t.is(typeof actual.parserPreset.opts, 'object');
t.is(typeof actual.parserPreset.parserOpts, 'object');
t.deepEqual(
actual.parserPreset.opts.parserOpts.headerPattern,
actual.parserPreset.parserOpts.headerPattern,
/^(\w*)(?:\((.*)\))?-(.*)$/
);
});
Expand Down
Loading