diff --git a/.gitignore b/.gitignore index 901d37cf45..7e2d6bc30a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea/ npm-debug.log* lerna-debug.log node_modules diff --git a/@commitlint/core/src/library/ensure-case.js b/@commitlint/core/src/library/ensure-case.js index f20358ed1e..64ba4a1163 100644 --- a/@commitlint/core/src/library/ensure-case.js +++ b/@commitlint/core/src/library/ensure-case.js @@ -1,11 +1,25 @@ +import {camelCase, kebabCase, snakeCase, upperFirst, startCase} from 'lodash'; + export default (raw = '', target = 'lowercase') => { - const normalized = String(raw); + const input = String(raw); switch (target) { + case 'camel-case': + return camelCase(input) === input; + case 'kebab-case': + return kebabCase(input) === input; + case 'snake-case': + return snakeCase(input) === input; + case 'pascal-case': + return upperFirst(camelCase(input)) === input; + case 'start-case': + return startCase(input) === input; + case 'upper-case': case 'uppercase': - return normalized.toUpperCase() === normalized; + return input.toUpperCase() === input; + case 'lower-case': case 'lowercase': default: - return normalized.toLowerCase() === normalized; + return input.toLowerCase() === input; } }; diff --git a/@commitlint/core/src/rules/scope-case.test.js b/@commitlint/core/src/rules/scope-case.test.js index c9adb7f482..313fd8acd3 100644 --- a/@commitlint/core/src/rules/scope-case.test.js +++ b/@commitlint/core/src/rules/scope-case.test.js @@ -6,14 +6,24 @@ const messages = { empty: 'chore: subject', lowercase: 'chore(scope): subject', mixedcase: 'chore(sCoPe): subject', - uppercase: 'chore(SCOPE): subject' + uppercase: 'chore(SCOPE): subject', + camelcase: 'chore(myScope): subject', + kebabcase: 'chore(my-scope): subject', + pascalcase: 'chore(MyScope): subject', + snakecase: 'chore(my_scope): subject', + startcase: 'chore(My Scope): subject' }; const parsed = { empty: parse(messages.empty), lowercase: parse(messages.lowercase), mixedcase: parse(messages.mixedcase), - uppercase: parse(messages.uppercase) + 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 empty scope should succeed for "never lowercase"', async t => { @@ -40,6 +50,66 @@ test('with empty scope should succeed for "always uppercase"', async t => { t.is(actual, expected); }); +test('with empty scope should succeed for "never camelcase"', async t => { + const [actual] = scopeCase(await parsed.empty, 'never', 'camel-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with empty scope should succeed for "always camelcase"', async t => { + const [actual] = scopeCase(await parsed.empty, 'never', 'camel-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with empty scope should succeed for "never kebabcase"', async t => { + const [actual] = scopeCase(await parsed.empty, 'never', 'kebab-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with empty scope should succeed for "always kebabcase"', async t => { + const [actual] = scopeCase(await parsed.empty, 'never', 'kebab-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with empty scope should succeed for "never pascalcase"', async t => { + const [actual] = scopeCase(await parsed.empty, 'never', 'pascal-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with empty scope should succeed for "always pascalcase"', async t => { + const [actual] = scopeCase(await parsed.empty, 'never', 'pascal-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with empty scope should succeed for "never snakecase"', async t => { + const [actual] = scopeCase(await parsed.empty, 'never', 'snake-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with empty scope should succeed for "always snakecase"', async t => { + const [actual] = scopeCase(await parsed.empty, 'never', 'snake-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with empty scope should succeed for "never startcase"', async t => { + const [actual] = scopeCase(await parsed.empty, 'never', 'start-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with empty scope should succeed for "always startcase"', async t => { + const [actual] = scopeCase(await parsed.empty, 'never', 'start-case'); + const expected = true; + t.is(actual, expected); +}); + test('with lowercase scope should fail for "never lowercase"', async t => { const [actual] = scopeCase(await parsed.lowercase, 'never', 'lowercase'); const expected = false; @@ -70,6 +140,106 @@ test('with mixedcase scope should succeed for "never uppercase"', async t => { t.is(actual, expected); }); +test('with kebabcase scope should succeed for "always lowercase"', async t => { + const [actual] = scopeCase(await parsed.kebabcase, 'always', 'lowercase'); + const expected = true; + t.is(actual, expected); +}); + +test('with kebabcase scope should fail for "always camelcase"', async t => { + const [actual] = scopeCase(await parsed.kebabcase, 'always', 'camel-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with kebabcase scope should fail for "always pascalcase"', async t => { + const [actual] = scopeCase(await parsed.kebabcase, 'always', 'pascal-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with kebabcase scope should succeed for "always kebabcase"', async t => { + const [actual] = scopeCase( + await parsed.kebabcase, + 'always', + 'kebabcase-case' + ); + const expected = true; + t.is(actual, expected); +}); + +test('with snakecase scope should succeed for "always lowercase"', async t => { + const [actual] = scopeCase(await parsed.snakecase, 'always', 'lowercase'); + const expected = true; + t.is(actual, expected); +}); + +test('with snakecase scope should fail for "always camelcase"', async t => { + const [actual] = scopeCase(await parsed.snakecase, 'always', 'camel-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with snakecase scope should fail for "always pascalcase"', async t => { + const [actual] = scopeCase(await parsed.snakecase, 'always', 'pascal-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with snakecase scope should succeed for "always snakecase"', async t => { + const [actual] = scopeCase(await parsed.snakecase, 'always', 'snake-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with camelcase scope should fail for "always lowercase"', async t => { + const [actual] = scopeCase(await parsed.camelcase, 'always', 'lowercase'); + const expected = false; + t.is(actual, expected); +}); + +test('with camelcase scope should succeed for "always camelcase"', async t => { + const [actual] = scopeCase(await parsed.camelcase, 'always', 'camel-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with camelcase scope should fail for "always kebabcase"', async t => { + const [actual] = scopeCase(await parsed.camelcase, 'always', 'kebab-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with camelcase scope should fail for "always pascalcase"', async t => { + const [actual] = scopeCase(await parsed.camelcase, 'always', 'pascal-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with pascalcase scope should fail for "always lowercase"', async t => { + const [actual] = scopeCase(await parsed.pascalcase, 'always', 'lowercase'); + const expected = false; + t.is(actual, expected); +}); + +test('with pascalcase scope should fail for "always kebabcase"', async t => { + const [actual] = scopeCase(await parsed.pascalcase, 'always', 'kebab-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with pascalcase scope should fail for "always camelcase"', async t => { + const [actual] = scopeCase(await parsed.pascalcase, 'always', 'camel-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with pascalcase scope should succeed for "always pascalcase"', async t => { + const [actual] = scopeCase(await parsed.pascalcase, 'always', 'pascal-case'); + const expected = true; + t.is(actual, expected); +}); + test('with mixedcase scope should fail for "always uppercase"', async t => { const [actual] = scopeCase(await parsed.mixedcase, 'always', 'uppercase'); const expected = false; diff --git a/@commitlint/core/src/rules/subject-case.test.js b/@commitlint/core/src/rules/subject-case.test.js index de0d5e71fe..8687e1eb3a 100644 --- a/@commitlint/core/src/rules/subject-case.test.js +++ b/@commitlint/core/src/rules/subject-case.test.js @@ -6,14 +6,24 @@ const messages = { empty: 'chore:\n', lowercase: 'chore: subject', mixedcase: 'chore: sUbJeCt', - uppercase: 'chore: SUBJECT' + uppercase: 'chore: SUBJECT', + camelcase: 'chore: subJect', + kebabcase: 'chore: sub-ject', + pascalcase: 'chore: SubJect', + snakecase: 'chore: sub_ject', + startcase: 'chore: Sub Ject' }; const parsed = { empty: parse(messages.empty), lowercase: parse(messages.lowercase), mixedcase: parse(messages.mixedcase), - uppercase: parse(messages.uppercase) + 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 empty subject should succeed for "never lowercase"', async t => { @@ -87,3 +97,157 @@ test('with lowercase subject should succeed for "always uppercase"', async t => const expected = true; t.is(actual, expected); }); + +test('with camelcase subject should fail for "always uppercase"', async t => { + const [actual] = subjectCase(await parsed.camelcase, 'always', 'uppercase'); + const expected = false; + t.is(actual, expected); +}); + +test('with camelcase subject should succeed for "never uppercase"', async t => { + const [actual] = subjectCase(await parsed.camelcase, 'never', 'uppercase'); + const expected = true; + t.is(actual, expected); +}); + +test('with camelcase subject should fail for "always pascalcase"', async t => { + const [actual] = subjectCase(await parsed.camelcase, 'always', 'pascal-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with camelcase subject should fail for "always kebabcase"', async t => { + const [actual] = subjectCase(await parsed.camelcase, 'always', 'kebab-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with camelcase subject should fail for "always snakecase"', async t => { + const [actual] = subjectCase(await parsed.camelcase, 'always', 'snake-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with camelcase subject should succeed for "always camelcase"', async t => { + const [actual] = subjectCase(await parsed.camelcase, 'always', 'camel-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with pascalcase subject should fail for "always uppercase"', async t => { + const [actual] = subjectCase(await parsed.pascalcase, 'always', 'uppercase'); + const expected = false; + t.is(actual, expected); +}); + +test('with pascalcase subject should succeed for "never uppercase"', async t => { + const [actual] = subjectCase(await parsed.pascalcase, 'never', 'uppercase'); + const expected = true; + t.is(actual, expected); +}); + +test('with pascalcase subject should succeed for "always pascalcase"', async t => { + const [actual] = subjectCase( + await parsed.pascalcase, + 'always', + 'pascal-case' + ); + const expected = true; + t.is(actual, expected); +}); + +test('with pascalcase subject should fail for "always kebabcase"', async t => { + const [actual] = subjectCase(await parsed.pascalcase, 'always', 'kebab-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with pascalcase subject should fail for "always snakecase"', async t => { + const [actual] = subjectCase(await parsed.pascalcase, 'always', 'snake-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with pascalcase subject should fail for "always camelcase"', async t => { + const [actual] = subjectCase(await parsed.pascalcase, 'always', 'camel-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with snakecase subject should fail for "always uppercase"', async t => { + const [actual] = subjectCase(await parsed.snakecase, 'always', 'uppercase'); + const expected = false; + t.is(actual, expected); +}); + +test('with snakecase subject should succeed for "never uppercase"', async t => { + const [actual] = subjectCase(await parsed.snakecase, 'never', 'uppercase'); + const expected = true; + t.is(actual, expected); +}); + +test('with snakecase subject should fail for "always pascalcase"', async t => { + const [actual] = subjectCase(await parsed.snakecase, 'always', 'pascal-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with snakecase subject should fail for "always kebabcase"', async t => { + const [actual] = subjectCase(await parsed.snakecase, 'always', 'kebab-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with snakecase subject should succeed for "always snakecase"', async t => { + const [actual] = subjectCase(await parsed.snakecase, 'always', 'snake-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with snakecase subject should fail for "always camelcase"', async t => { + const [actual] = subjectCase(await parsed.snakecase, 'always', 'camel-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase subject should fail for "always uppercase"', async t => { + const [actual] = subjectCase(await parsed.startcase, 'always', 'uppercase'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase subject should succeed for "never uppercase"', async t => { + const [actual] = subjectCase(await parsed.startcase, 'never', 'uppercase'); + const expected = true; + t.is(actual, expected); +}); + +test('with startcase subject should fail for "always pascalcase"', async t => { + const [actual] = subjectCase(await parsed.startcase, 'always', 'pascal-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase subject should fail for "always kebabcase"', async t => { + const [actual] = subjectCase(await parsed.startcase, 'always', 'kebab-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase subject should fail for "always snakecase"', async t => { + const [actual] = subjectCase(await parsed.startcase, 'always', 'snake-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase subject should fail for "always camelcase"', async t => { + const [actual] = subjectCase(await parsed.startcase, 'always', 'camel-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase subject should succeed for "always startcase"', async t => { + const [actual] = subjectCase(await parsed.startcase, 'always', 'start-case'); + const expected = true; + t.is(actual, expected); +}); diff --git a/@commitlint/core/src/rules/type-case.test.js b/@commitlint/core/src/rules/type-case.test.js index 1729f5686a..6c34fd11e5 100644 --- a/@commitlint/core/src/rules/type-case.test.js +++ b/@commitlint/core/src/rules/type-case.test.js @@ -6,14 +6,24 @@ const messages = { empty: '(scope): subject', lowercase: 'type: subject', mixedcase: 'tYpE: subject', - uppercase: 'TYPE: subject' + uppercase: 'TYPE: subject', + camelcase: 'tyPe: subject', + pascalcase: 'TyPe: subject', + snakecase: 'ty_pe: subject', + kebabcase: 'ty-pe: subject', + startcase: 'Ty Pe: subject' }; const parsed = { empty: parse(messages.empty), lowercase: parse(messages.lowercase), mixedcase: parse(messages.mixedcase), - uppercase: parse(messages.uppercase) + uppercase: parse(messages.uppercase), + camelcase: parse(messages.camelcase), + pascalcase: parse(messages.pascalcase), + snakecase: parse(messages.snakecase), + kebabcase: parse(messages.kebabcase), + startcase: parse(messages.startcase) }; test('with empty type should succeed for "never lowercase"', async t => { @@ -87,3 +97,171 @@ test('with lowercase type should succeed for "always uppercase"', async t => { const expected = true; t.is(actual, expected); }); + +test('with camelcase type should fail for "always uppercase"', async t => { + const [actual] = typeCase(await parsed.camelcase, 'always', 'uppercase'); + const expected = false; + t.is(actual, expected); +}); + +test('with camelcase type should succeed for "never uppercase"', async t => { + const [actual] = typeCase(await parsed.camelcase, 'never', 'uppercase'); + const expected = true; + t.is(actual, expected); +}); + +test('with camelcase type should fail for "always pascalcase"', async t => { + const [actual] = typeCase(await parsed.camelcase, 'always', 'pascal-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with camelcase type should fail for "always kebabcase"', async t => { + const [actual] = typeCase(await parsed.camelcase, 'always', 'kebab-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with camelcase type should fail for "always snakecase"', async t => { + const [actual] = typeCase(await parsed.camelcase, 'always', 'snake-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with camelcase type should fail for "always startcase"', async t => { + const [actual] = typeCase(await parsed.camelcase, 'always', 'start-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with camelcase type should succeed for "always camelcase"', async t => { + const [actual] = typeCase(await parsed.camelcase, 'always', 'camel-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with pascalcase type should fail for "always uppercase"', async t => { + const [actual] = typeCase(await parsed.pascalcase, 'always', 'uppercase'); + const expected = false; + t.is(actual, expected); +}); + +test('with pascalcase type should succeed for "never uppercase"', async t => { + const [actual] = typeCase(await parsed.pascalcase, 'never', 'uppercase'); + const expected = true; + t.is(actual, expected); +}); + +test('with pascalcase type should fail for "always camelcase"', async t => { + const [actual] = typeCase(await parsed.pascalcase, 'always', 'camel-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with pascalcase type should fail for "always kebabcase"', async t => { + const [actual] = typeCase(await parsed.pascalcase, 'always', 'kebab-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with pascalcase type should fail for "always snakecase"', async t => { + const [actual] = typeCase(await parsed.pascalcase, 'always', 'snake-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with pascalcase type should fail for "always startcase"', async t => { + const [actual] = typeCase(await parsed.pascalcase, 'always', 'start-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with pascalcase type should succeed for "always pascalcase"', async t => { + const [actual] = typeCase(await parsed.pascalcase, 'always', 'pascal-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with snakecase type should fail for "always uppercase"', async t => { + const [actual] = typeCase(await parsed.snakecase, 'always', 'uppercase'); + const expected = false; + t.is(actual, expected); +}); + +test('with snakecase type should succeed for "never uppercase"', async t => { + const [actual] = typeCase(await parsed.snakecase, 'never', 'uppercase'); + const expected = true; + t.is(actual, expected); +}); + +test('with snakecase type should fail for "always camelcase"', async t => { + const [actual] = typeCase(await parsed.snakecase, 'always', 'camel-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with snakecase type should fail for "always kebabcase"', async t => { + const [actual] = typeCase(await parsed.snakecase, 'always', 'kebab-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with snakecase type should succeed for "always snakecase"', async t => { + const [actual] = typeCase(await parsed.snakecase, 'always', 'snake-case'); + const expected = true; + t.is(actual, expected); +}); + +test('with snakecase type should fail for "always pascalcase"', async t => { + const [actual] = typeCase(await parsed.snakecase, 'always', 'pascal-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with snakecase type should fail for "always start case"', async t => { + const [actual] = typeCase(await parsed.snakecase, 'always', 'start-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase type should fail for "always uppercase"', async t => { + const [actual] = typeCase(await parsed.startcase, 'always', 'uppercase'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase type should succeed for "never uppercase"', async t => { + const [actual] = typeCase(await parsed.startcase, 'never', 'uppercase'); + const expected = true; + t.is(actual, expected); +}); + +test('with startcase type should fail for "always camelcase"', async t => { + const [actual] = typeCase(await parsed.startcase, 'always', 'camel-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase type should fail for "always kebabcase"', async t => { + const [actual] = typeCase(await parsed.startcase, 'always', 'kebab-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase type should fail for "always snakecase"', async t => { + const [actual] = typeCase(await parsed.startcase, 'always', 'snake-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase type should fail for "always pascalcase"', async t => { + const [actual] = typeCase(await parsed.startcase, 'always', 'pascal-case'); + const expected = false; + t.is(actual, expected); +}); + +test('with startcase type should succeed for "always startcase"', async t => { + const [actual] = typeCase(await parsed.startcase, 'always', 'start-case'); + const expected = true; + t.is(actual, expected); +}); diff --git a/docs/reference-rules.md b/docs/reference-rules.md index ead3a886a5..8b9fbef9bd 100644 --- a/docs/reference-rules.md +++ b/docs/reference-rules.md @@ -146,6 +146,18 @@ Rule configurations are either of type `array` residing on a key with the rule's ```js 'lowerCase' ``` +* **possible values** +```js + [ + 'lower-case', // default + 'upper-case', // UPPERCASE + 'camel-case', // camelCase + 'kebab-case', // kebab-case + 'pascal-case', // PascalCase + 'snake-case', // snake_case + 'start-case' // Start Case + ] +``` #### scope-empty * **condition**: `scope` is empty @@ -173,6 +185,18 @@ Rule configurations are either of type `array` residing on a key with the rule's ```js 'lowerCase' ``` +* **possible values** +```js + [ + 'lower-case', // default + 'upper-case', // UPPERCASE + 'camel-case', // camelCase + 'kebab-case', // kebab-case + 'pascal-case', // PascalCase + 'snake-case', // snake_case + 'start-case' // Start Case + ] +``` #### subject-empty * **condition**: `subject` is empty @@ -243,6 +267,18 @@ Rule configurations are either of type `array` residing on a key with the rule's ```js 'lowerCase' ``` +* **possible values** +```js + [ + 'lower-case', // default + 'upper-case', // UPPERCASE + 'camel-case', // camelCase + 'kebab-case', // kebab-case + 'pascal-case', // PascalCase + 'snake-case', // snake_case + 'start-case' // Start Case + ] +``` #### type-empty * **condition**: `type` is empty diff --git a/package.json b/package.json index 1a1d02324d..5ec707e0a9 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "name": "Mario Nebl", "email": "hello@herebecode.com" }, + "dependencies": {}, "devDependencies": { "docsify-cli": "^4.1.8", "eslint-config-prettier": "^2.3.0",