Skip to content

Commit

Permalink
feat(config-conventional): footer/body-max-line (#436)
Browse files Browse the repository at this point in the history
* feat(config-conventional): footer/body-max-line

Co-authored-by: Mario Nebl <marionebl@users.noreply.github.com>

BREAKING CHANGE
Introduces new rules on `error` level that validate the line length of `footer`
and `body`. This might create negative linting reports for commits that previously where passing.
  • Loading branch information
Fetz committed Feb 18, 2020
1 parent 4443062 commit 8b394c9
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 0 deletions.
94 changes: 94 additions & 0 deletions @commitlint/config-conventional/README.md
Expand Up @@ -24,6 +24,7 @@ Consult [docs/rules](https://conventional-changelog.github.io/commitlint/#/refer

- **condition**: `type` is found in value
- **rule**: `always`
- **level**: `error`
- **value**

```
Expand Down Expand Up @@ -51,6 +52,7 @@ echo "fix: some message" # passes

- **description**: `type` is in case `value`
- **rule**: `always`
- **level**: `error`
- **value**
```
'lowerCase'
Expand All @@ -65,6 +67,7 @@ echo "fix: some message" # passes

- **condition**: `type` is empty
- **rule**: `never`
- **level**: `error`

```sh
echo ": some message" # fails
Expand All @@ -75,6 +78,7 @@ echo "fix: some message" # passes

- **condition**: `scope` is in case `value`
- **rule**: `always`
- **level**: `error`

```
'lowerCase'
Expand All @@ -89,6 +93,7 @@ echo "fix(scope): some message" # passes

- **condition**: `subject` is in one of the cases `['sentence-case', 'start-case', 'pascal-case', 'upper-case']`
- **rule**: `never`
- **level**: `error`

```sh
echo "fix(SCOPE): Some message" # fails
Expand All @@ -103,6 +108,7 @@ echo "fix(scope): some Message" # passes

- **condition**: `subject` is empty
- **rule**: `never`
- **level**: `error`

```sh
echo "fix:" # fails
Expand All @@ -113,6 +119,7 @@ echo "fix: some message" # passes

- **condition**: `subject` ends with `value`
- **rule**: `never`
- **level**: `error`
- **value**

```
Expand All @@ -128,6 +135,7 @@ echo "fix: some message" # passes

- **condition**: `header` has `value` or less characters
- **rule**: `always`
- **level**: `error`
- **value**

```
Expand All @@ -138,3 +146,89 @@ echo "fix: some message" # passes
echo "fix: some message that is way too long and breaks the line max-length by several characters" # fails
echo "fix: some message" # passes
```

#### footer-leading-blank

- **condition**: `footer` should have a leading blank line
- **rule**: `always`
- level: `warning`
- **value**

```
100
```

```sh
echo "fix: some message
BREAKING CHANGE: It will be significant" # warning

echo "fix: some message
BREAKING CHANGE: It will be significant" # passes
```

#### footer-max-line-length

- **condition**: `footer` each line has `value` or less characters
- **rule**: `always`
- level: `error`
- **value**

```
100
```

```sh
echo "fix: some message
BREAKING CHANGE: footer with multiple lines
has a message that is way too long and will break the line rule 'line-max-length' by several characters" # fails

echo "fix: some message
BREAKING CHANGE: footer with multiple lines
but still no line is too long" # passes
```

#### body-leading-blank

- **condition**: `body` should have a leading blank line
- **rule**: `always`
- level: `warning`
- **value**

```js
100;
```

```sh
echo "fix: some message
body" # warning

echo "fix: some message
body" # passes
```

#### body-max-line-length

- **condition**: `body` each line has `value` or less characters
- **rule**: `always`
- level: `error`
- **value**

```js
100;
```

```sh
echo "fix: some message
body with multiple lines
has a message that is way too long and will break the line rule 'line-max-length' by several characters" # fails

echo "fix: some message
body with multiple lines
but still no line is too long" # passes
```
2 changes: 2 additions & 0 deletions @commitlint/config-conventional/index.js
Expand Up @@ -2,7 +2,9 @@ module.exports = {
parserPreset: 'conventional-changelog-conventionalcommits',
rules: {
'body-leading-blank': [1, 'always'],
'body-max-line-length': [2, 'always', 100],
'footer-leading-blank': [1, 'always'],
'footer-max-line-length': [2, 'always', 100],
'header-max-length': [2, 'always', 100],
'scope-case': [2, 'always', 'lower-case'],
'subject-case': [
Expand Down
214 changes: 214 additions & 0 deletions @commitlint/config-conventional/index.test.js
@@ -0,0 +1,214 @@
import lint from '@commitlint/lint';
import {rules} from '.';

const messages = {
invalidTypeEnum: 'foo: some message',
invalidTypeCase: 'FIX: some message',
invalidTypeEmpty: ': some message',
invalidScopeCase: 'fix(SCOPE): some message',
invalidSubjectCases: [
'fix(scope): Some message',
'fix(scope): Some Message',
'fix(scope): SomeMessage',
'fix(scope): SOMEMESSAGE'
],
invalidSubjectEmpty: 'fix:',
invalidSubjectFullStop: 'fix: some message.',
invalidHeaderMaxLength:
'fix: some message that is way too long and breaks the line max-length by several characters since the max is 100',
warningFooterLeadingBlank:
'fix: some message\n\nbody\nBREAKING CHANGE: It will be significant',
invalidFooterMaxLineLength:
'fix: some message\n\nbody\n\nBREAKING CHANGE: footer with multiple lines\nhas a message that is way too long and will break the line rule "line-max-length" by several characters',
warningBodyLeadingBlank: 'fix: some message\nbody',
invalidBodyMaxLineLength:
'fix: some message\n\nbody with multiple lines\nhas a message that is way too long and will break the line rule "line-max-length" by several characters',
validMessages: [
'fix: some message',
'fix(scope): some message',
'fix(scope): some Message',
'fix(scope): some message\n\nBREAKING CHANGE: it will be significant!',
'fix(scope): some message\n\nbody'
]
};

const errors = {
typeEnum: {
level: 2,
message:
'type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test]',
name: 'type-enum',
valid: false
},
typeCase: {
level: 2,
message: 'type must be lower-case',
name: 'type-case',
valid: false
},
typeEmpty: {
level: 2,
message: 'type may not be empty',
name: 'type-empty',
valid: false
},
scopeCase: {
level: 2,
message: 'scope must be lower-case',
name: 'scope-case',
valid: false
},
subjectCase: {
level: 2,
message:
'subject must not be sentence-case, start-case, pascal-case, upper-case',
name: 'subject-case',
valid: false
},
subjectEmpty: {
level: 2,
message: 'subject may not be empty',
name: 'subject-empty',
valid: false
},
subjectFullStop: {
level: 2,
message: 'subject may not end with full stop',
name: 'subject-full-stop',
valid: false
},
headerMaxLength: {
level: 2,
message:
'header must not be longer than 100 characters, current length is 112',
name: 'header-max-length',
valid: false
},
footerMaxLineLength: {
level: 2,
message: "footer's lines must not be longer than 100 characters",
name: 'footer-max-line-length',
valid: false
},
bodyMaxLineLength: {
level: 2,
message: "body's lines must not be longer than 100 characters",
name: 'body-max-line-length',
valid: false
}
};

const warnings = {
footerLeadingBlank: {
level: 1,
message: 'footer must have leading blank line',
name: 'footer-leading-blank',
valid: false
},
bodyLeadingBlank: {
level: 1,
message: 'body must have leading blank line',
name: 'body-leading-blank',
valid: false
}
};

test('type-enum', async () => {
const result = await lint(messages.invalidTypeEnum, rules);

expect(result.valid).toBe(false);
expect(result.errors).toEqual([errors.typeEnum]);
});

test('type-case', async () => {
const result = await lint(messages.invalidTypeCase, rules);

expect(result.valid).toBe(false);
expect(result.errors).toEqual([errors.typeCase, errors.typeEnum]);
});

test('type-empty', async () => {
const result = await lint(messages.invalidTypeEmpty, rules);

expect(result.valid).toBe(false);
expect(result.errors).toEqual([errors.typeEmpty]);
});

test('scope-case', async () => {
const result = await lint(messages.invalidScopeCase, rules);

expect(result.valid).toBe(false);
expect(result.errors).toEqual([errors.scopeCase]);
});

test('subject-case', async () => {
const invalidInputs = await Promise.all(
messages.invalidSubjectCases.map(invalidInput => lint(invalidInput, rules))
);

invalidInputs.forEach(result => {
expect(result.valid).toBe(false);
expect(result.errors).toEqual([errors.subjectCase]);
});
});

test('subject-empty', async () => {
const result = await lint(messages.invalidSubjectEmpty, rules);

expect(result.valid).toBe(false);
expect(result.errors).toEqual([errors.subjectEmpty, errors.typeEmpty]);
});

test('subject-full-stop', async () => {
const result = await lint(messages.invalidSubjectFullStop, rules);

expect(result.valid).toBe(false);
expect(result.errors).toEqual([errors.subjectFullStop]);
});

test('header-max-length', async () => {
const result = await lint(messages.invalidHeaderMaxLength, rules);

expect(result.valid).toBe(false);
expect(result.errors).toEqual([errors.headerMaxLength]);
});

test('footer-leading-blank', async () => {
const result = await lint(messages.warningFooterLeadingBlank, rules);

expect(result.valid).toBe(true);
expect(result.warnings).toEqual([warnings.footerLeadingBlank]);
});

test('footer-max-line-length', async () => {
const result = await lint(messages.invalidFooterMaxLineLength, rules);

expect(result.valid).toBe(false);
expect(result.errors).toEqual([errors.footerMaxLineLength]);
});

test('body-leading-blank', async () => {
const result = await lint(messages.warningBodyLeadingBlank, rules);

expect(result.valid).toBe(true);
expect(result.warnings).toEqual([warnings.bodyLeadingBlank]);
});

test('body-max-line-length', async () => {
const result = await lint(messages.invalidBodyMaxLineLength, rules);

expect(result.valid).toBe(false);
expect(result.errors).toEqual([errors.bodyMaxLineLength]);
});

test('valid messages', async () => {
const validInputs = await Promise.all(
messages.validMessages.map(input => lint(input, rules))
);

validInputs.forEach(result => {
expect(result.valid).toBe(true);
expect(result.errors).toEqual([]);
expect(result.warnings).toEqual([]);
});
});
1 change: 1 addition & 0 deletions @commitlint/config-conventional/package.json
Expand Up @@ -32,6 +32,7 @@
"node": ">=8"
},
"devDependencies": {
"@commitlint/lint": "^8.3.4",
"@commitlint/utils": "^8.3.4"
},
"dependencies": {
Expand Down
10 changes: 10 additions & 0 deletions @commitlint/config-conventional/tsconfig.json
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.shared.json",
"compilerOptions": {
"composite": true,
"rootDir": "./src",
"outDir": "./lib"
},
"include": ["./**/*.ts"],
"exclude": ["./**/*.test.ts"]
}

1 comment on commit 8b394c9

@khashayarnazari

This comment was marked as off-topic.

Please sign in to comment.