Skip to content

Commit

Permalink
feat(rules): create header-case and header-full-stop rules (#547)
Browse files Browse the repository at this point in the history
* feat(rules): create header-full-stop rule

* feat(rules): create header-case rule

These are simplified versions of the subject-* rules. Headers are always present, so we don’t need to check for empty-ness. If headers are not defined, the parser will throw with an error “expecting raw commit”.
  • Loading branch information
byCedric authored Jan 27, 2019
1 parent fa6168a commit 4c36cbd
Show file tree
Hide file tree
Showing 6 changed files with 431 additions and 0 deletions.
34 changes: 34 additions & 0 deletions @commitlint/rules/src/header-case.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as ensure from '@commitlint/ensure';
import message from '@commitlint/message';

const negated = when => when === 'never';

export default (parsed, when, value) => {
const {header} = parsed;

if (typeof header !== 'string' || !header.match(/^[a-z]/i)) {
return [true];
}

const checks = (Array.isArray(value) ? value : [value]).map(check => {
if (typeof check === 'string') {
return {
when: 'always',
case: check
};
}
return check;
});

const result = checks.some(check => {
const r = ensure.case(header, check.case);
return negated(check.when) ? !r : r;
});

const list = checks.map(c => c.case).join(', ');

return [
negated(when) ? !result : result,
message([`header must`, negated(when) ? `not` : null, `be ${list}`])
];
};
318 changes: 318 additions & 0 deletions @commitlint/rules/src/header-case.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
import test from 'ava';
import parse from '@commitlint/parse';
import headerCase from './header-case';

const messages = {
numeric: '1.0.0\n',
lowercase: 'header\n',
mixedcase: 'hEaDeR\n',
uppercase: 'HEADER\n',
camelcase: 'heaDer\n',
kebabcase: 'hea-der\n',
pascalcase: 'HeaDer\n',
snakecase: 'hea_der\n',
startcase: 'Hea Der\n'
};

const parsed = {
numeric: parse(messages.numeric),
lowercase: parse(messages.lowercase),
mixedcase: parse(messages.mixedcase),
uppercase: parse(messages.uppercase),
camelcase: parse(messages.camelcase),
kebabcase: parse(messages.kebabcase),
pascalcase: parse(messages.pascalcase),
snakecase: parse(messages.snakecase),
startcase: parse(messages.startcase)
};

test('with lowercase header should fail for "never lowercase"', async t => {
const [actual] = headerCase(await parsed.lowercase, 'never', 'lowercase');
const expected = false;
t.is(actual, expected);
});

test('with lowercase header should succeed for "always lowercase"', async t => {
const [actual] = headerCase(await parsed.lowercase, 'always', 'lowercase');
const expected = true;
t.is(actual, expected);
});

test('with mixedcase header should succeed for "never lowercase"', async t => {
const [actual] = headerCase(await parsed.mixedcase, 'never', 'lowercase');
const expected = true;
t.is(actual, expected);
});

test('with mixedcase header should fail for "always lowercase"', async t => {
const [actual] = headerCase(await parsed.mixedcase, 'always', 'lowercase');
const expected = false;
t.is(actual, expected);
});

test('with mixedcase header should succeed for "never uppercase"', async t => {
const [actual] = headerCase(await parsed.mixedcase, 'never', 'uppercase');
const expected = true;
t.is(actual, expected);
});

test('with mixedcase header should fail for "always uppercase"', async t => {
const [actual] = headerCase(await parsed.mixedcase, 'always', 'uppercase');
const expected = false;
t.is(actual, expected);
});

test('with uppercase header should fail for "never uppercase"', async t => {
const [actual] = headerCase(await parsed.uppercase, 'never', 'uppercase');
const expected = false;
t.is(actual, expected);
});

test('with lowercase header should succeed for "always uppercase"', async t => {
const [actual] = headerCase(await parsed.uppercase, 'always', 'uppercase');
const expected = true;
t.is(actual, expected);
});

test('with camelcase header should fail for "always uppercase"', async t => {
const [actual] = headerCase(await parsed.camelcase, 'always', 'uppercase');
const expected = false;
t.is(actual, expected);
});

test('with camelcase header should succeed for "never uppercase"', async t => {
const [actual] = headerCase(await parsed.camelcase, 'never', 'uppercase');
const expected = true;
t.is(actual, expected);
});

test('with camelcase header should fail for "always pascalcase"', async t => {
const [actual] = headerCase(await parsed.camelcase, 'always', 'pascal-case');
const expected = false;
t.is(actual, expected);
});

test('with camelcase header should fail for "always kebabcase"', async t => {
const [actual] = headerCase(await parsed.camelcase, 'always', 'kebab-case');
const expected = false;
t.is(actual, expected);
});

test('with camelcase header should fail for "always snakecase"', async t => {
const [actual] = headerCase(await parsed.camelcase, 'always', 'snake-case');
const expected = false;
t.is(actual, expected);
});

test('with camelcase header should succeed for "always camelcase"', async t => {
const [actual] = headerCase(await parsed.camelcase, 'always', 'camel-case');
const expected = true;
t.is(actual, expected);
});

test('with pascalcase header should fail for "always uppercase"', async t => {
const [actual] = headerCase(await parsed.pascalcase, 'always', 'uppercase');
const expected = false;
t.is(actual, expected);
});

test('with pascalcase header should succeed for "never uppercase"', async t => {
const [actual] = headerCase(await parsed.pascalcase, 'never', 'uppercase');
const expected = true;
t.is(actual, expected);
});

test('with pascalcase header should succeed for "always pascalcase"', async t => {
const [actual] = headerCase(await parsed.pascalcase, 'always', 'pascal-case');
const expected = true;
t.is(actual, expected);
});

test('with pascalcase header should fail for "always kebabcase"', async t => {
const [actual] = headerCase(await parsed.pascalcase, 'always', 'kebab-case');
const expected = false;
t.is(actual, expected);
});

test('with pascalcase header should fail for "always snakecase"', async t => {
const [actual] = headerCase(await parsed.pascalcase, 'always', 'snake-case');
const expected = false;
t.is(actual, expected);
});

test('with pascalcase header should fail for "always camelcase"', async t => {
const [actual] = headerCase(await parsed.pascalcase, 'always', 'camel-case');
const expected = false;
t.is(actual, expected);
});

test('with snakecase header should fail for "always uppercase"', async t => {
const [actual] = headerCase(await parsed.snakecase, 'always', 'uppercase');
const expected = false;
t.is(actual, expected);
});

test('with snakecase header should succeed for "never uppercase"', async t => {
const [actual] = headerCase(await parsed.snakecase, 'never', 'uppercase');
const expected = true;
t.is(actual, expected);
});

test('with snakecase header should fail for "always pascalcase"', async t => {
const [actual] = headerCase(await parsed.snakecase, 'always', 'pascal-case');
const expected = false;
t.is(actual, expected);
});

test('with snakecase header should fail for "always kebabcase"', async t => {
const [actual] = headerCase(await parsed.snakecase, 'always', 'kebab-case');
const expected = false;
t.is(actual, expected);
});

test('with snakecase header should succeed for "always snakecase"', async t => {
const [actual] = headerCase(await parsed.snakecase, 'always', 'snake-case');
const expected = true;
t.is(actual, expected);
});

test('with snakecase header should fail for "always camelcase"', async t => {
const [actual] = headerCase(await parsed.snakecase, 'always', 'camel-case');
const expected = false;
t.is(actual, expected);
});

test('with startcase header should fail for "always uppercase"', async t => {
const [actual] = headerCase(await parsed.startcase, 'always', 'uppercase');
const expected = false;
t.is(actual, expected);
});

test('with startcase header should succeed for "never uppercase"', async t => {
const [actual] = headerCase(await parsed.startcase, 'never', 'uppercase');
const expected = true;
t.is(actual, expected);
});

test('with startcase header should fail for "always pascalcase"', async t => {
const [actual] = headerCase(await parsed.startcase, 'always', 'pascal-case');
const expected = false;
t.is(actual, expected);
});

test('with startcase header should fail for "always kebabcase"', async t => {
const [actual] = headerCase(await parsed.startcase, 'always', 'kebab-case');
const expected = false;
t.is(actual, expected);
});

test('with startcase header should fail for "always snakecase"', async t => {
const [actual] = headerCase(await parsed.startcase, 'always', 'snake-case');
const expected = false;
t.is(actual, expected);
});

test('with startcase header should fail for "always camelcase"', async t => {
const [actual] = headerCase(await parsed.startcase, 'always', 'camel-case');
const expected = false;
t.is(actual, expected);
});

test('with startcase header should succeed for "always startcase"', async t => {
const [actual] = headerCase(await parsed.startcase, 'always', 'start-case');
const expected = true;
t.is(actual, expected);
});

test('should use expected message with "always"', async t => {
const [, message] = headerCase(
await parsed.uppercase,
'always',
'lower-case'
);
t.true(message.indexOf('must be lower-case') > -1);
});

test('should use expected message with "never"', async t => {
const [, message] = headerCase(await parsed.uppercase, 'never', 'upper-case');
t.true(message.indexOf('must not be upper-case') > -1);
});

test('with uppercase scope should succeed for "always [uppercase, lowercase]"', async t => {
const [actual] = headerCase(await parsed.uppercase, 'always', [
'uppercase',
'lowercase'
]);
const expected = true;
t.is(actual, expected);
});

test('with lowercase header should succeed for "always [uppercase, lowercase]"', async t => {
const [actual] = headerCase(await parsed.lowercase, 'always', [
'uppercase',
'lowercase'
]);
const expected = true;
t.is(actual, expected);
});

test('with mixedcase header should fail for "always [uppercase, lowercase]"', async t => {
const [actual] = headerCase(await parsed.mixedcase, 'always', [
'uppercase',
'lowercase'
]);
const expected = false;
t.is(actual, expected);
});

test('with mixedcase header should pass for "always [uppercase, lowercase, camel-case]"', async t => {
const [actual] = headerCase(await parsed.mixedcase, 'always', [
'uppercase',
'lowercase',
'camel-case'
]);
const expected = true;
t.is(actual, expected);
});

test('with mixedcase scope should pass for "never [uppercase, lowercase]"', async t => {
const [actual] = headerCase(await parsed.mixedcase, 'never', [
'uppercase',
'lowercase'
]);
const expected = true;
t.is(actual, expected);
});

test('with uppercase scope should fail for "never [uppercase, lowercase]"', async t => {
const [actual] = headerCase(await parsed.uppercase, 'never', [
'uppercase',
'lowercase'
]);
const expected = false;
t.is(actual, expected);
});

test('with numeric header should succeed for "never lowercase"', async t => {
const [actual] = headerCase(await parsed.numeric, 'never', 'lowercase');
const expected = true;
t.is(actual, expected);
});

test('with numeric header should succeed for "always lowercase"', async t => {
const [actual] = headerCase(await parsed.numeric, 'always', 'lowercase');
const expected = true;
t.is(actual, expected);
});

test('with numeric header should succeed for "never uppercase"', async t => {
const [actual] = headerCase(await parsed.numeric, 'never', 'uppercase');
const expected = true;
t.is(actual, expected);
});

test('with numeric header should succeed for "always uppercase"', async t => {
const [actual] = headerCase(await parsed.numeric, 'always', 'uppercase');
const expected = true;
t.is(actual, expected);
});
12 changes: 12 additions & 0 deletions @commitlint/rules/src/header-full-stop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import message from '@commitlint/message';

export default (parsed, when, value) => {
const {header} = parsed;
const negated = when === 'never';
const hasStop = header[header.length - 1] === value;

return [
negated ? !hasStop : hasStop,
message(['header', negated ? 'may not' : 'must', 'end with full stop'])
];
};
Loading

0 comments on commit 4c36cbd

Please sign in to comment.