From e55edb54ed2bfc8cb1e2e9205831930c35ce47d8 Mon Sep 17 00:00:00 2001 From: Gabriel Terwesten Date: Fri, 2 Sep 2022 14:32:54 +0200 Subject: [PATCH] feat(conventional_commit)!: allow custom type Any type that matches [a-zA-Z0-9_]+ is allowed. BREAKING CHANGE: - Remove SemverReleaseType. - Remove ConventionalCommit.isVersionableCommit. - Remove ConventionalCommit.semverReleaseType. Fixes #363 --- packages/conventional_commit/README.md | 9 +- .../example/conventional_commit.dart | 10 +- .../lib/conventional_commit.dart | 109 ++++++------------ .../test/conventional_commit_test.dart | 55 +-------- 4 files changed, 42 insertions(+), 141 deletions(-) diff --git a/packages/conventional_commit/README.md b/packages/conventional_commit/README.md index b599587e9..2928bade7 100644 --- a/packages/conventional_commit/README.md +++ b/packages/conventional_commit/README.md @@ -7,7 +7,8 @@ --- -Parse a git commit message into a [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) format. +Parse a git commit message into a +[Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) format. ## Example @@ -50,17 +51,11 @@ void main() { print(parsedCommit.breakingChangeDescription); // : This is a breaking change because of X Y Z. - print(parsedCommit.semverReleaseType); - // : SemverReleaseType.major - print(parsedCommit.footers); // : ['Co-authored-by: @Salakar', 'Refs #123 #456'] print(parsedCommit.isMergeCommit); // : false - - print(parsedCommit.isVersionableCommit); - // : true } ``` diff --git a/packages/conventional_commit/example/conventional_commit.dart b/packages/conventional_commit/example/conventional_commit.dart index 6996b524b..c04e9b30e 100644 --- a/packages/conventional_commit/example/conventional_commit.dart +++ b/packages/conventional_commit/example/conventional_commit.dart @@ -2,7 +2,7 @@ import 'package:conventional_commit/conventional_commit.dart'; const commitMessageExample = ''' -feat(cool): An exciting new feature. +feat(cool): An exciting new feature. A body describing this commit in more detail. @@ -37,17 +37,9 @@ void main() { print(parsedCommit.breakingChangeDescription); // : This is a breaking change because of X Y Z. - // Note this api may be removed in future. - print(parsedCommit.semverReleaseType); - // : SemverReleaseType.major - print(parsedCommit.footers); // : ['Co-authored-by: @Salakar', 'Refs #123 #456'] print(parsedCommit.isMergeCommit); // : false - - // Note this api may be removed in future. - print(parsedCommit.isVersionableCommit); - // : true } diff --git a/packages/conventional_commit/lib/conventional_commit.dart b/packages/conventional_commit/lib/conventional_commit.dart index 9ea0c3901..710dfbd96 100644 --- a/packages/conventional_commit/lib/conventional_commit.dart +++ b/packages/conventional_commit/lib/conventional_commit.dart @@ -15,8 +15,10 @@ * */ -final _conventionalCommitRegex = RegExp( - r'(?build|chore|ci|docs|feat|fix|bug|perf|refactor|revert|style|test)(?\([a-zA-Z0-9_,\s\*]+\)?((?=:\s?)|(?=!:\s?)))?(?!)?(?:\s?.*)?|^(?Merge \w+)', +final _mergeCommitPrefixRegex = RegExp('^Merged? (.*?:)?'); + +final _conventionalCommitHeaderRegex = RegExp( + r'(?[a-zA-Z0-9_]+)(\((?[a-zA-Z0-9_,\s\*]+)\))?(?!)?: ?(?.+)', ); final _breakingChangeRegex = @@ -27,18 +29,6 @@ final _footerRegex = RegExp( multiLine: true, ); -/// Indicates the semver release type this commit message creates. -enum SemverReleaseType { - /// A patch release indicates non-breaking changes (e.g. bug fixes). - patch, - - /// Indicates new API changes have been made (e.g. new features). - minor, - - /// A major release is when the breaking changes have been introduced. - major, -} - /// A representation of a parsed conventional commit message. /// /// Parsing is based upon the Conventional Commits 1.0.0 specification available @@ -75,30 +65,33 @@ class ConventionalCommit { /// ``` static ConventionalCommit? tryParse(String commitMessage) { final header = commitMessage.split('\n')[0]; - final match = _conventionalCommitRegex.firstMatch(header); + final mergeCommitPrefixMatch = _mergeCommitPrefixRegex.firstMatch(header); + final isMergeCommit = mergeCommitPrefixMatch != null; + final headerMatch = _conventionalCommitHeaderRegex.firstMatch( + isMergeCommit ? header.substring(mergeCommitPrefixMatch!.end) : header, + ); - if (match == null) { + if (headerMatch == null) { + if (isMergeCommit) { + return ConventionalCommit._( + header: header, + isMergeCommit: isMergeCommit, + isBreakingChange: false, + scopes: [], + ); + } return null; } - final isMergeCommit = match.namedGroup('merge') != null; - if (isMergeCommit) { - return ConventionalCommit._( - header: header, - isMergeCommit: isMergeCommit, - isBreakingChange: false, - scopes: [], - ); - } - - final type = match.namedGroup('type'); - var description = (match.namedGroup('description') ?? '').trim(); - description = description.replaceAll(RegExp(r'^:\s'), '').trim(); - if (description.isEmpty) { - return null; - } + final type = headerMatch.namedGroup('type')!.toLowerCase(); + final scopes = (headerMatch.namedGroup('scope') ?? '') + .split(',') + .map((scope) => scope.trim()) + .where((scope) => scope.isNotEmpty) + .toList(); + final description = headerMatch.namedGroup('description')!.trim(); - final isBreakingChange = match.namedGroup('breaking') != null || + final isBreakingChange = headerMatch.namedGroup('breaking') != null || commitMessage.contains('BREAKING: ') || commitMessage.contains('BREAKING CHANGE: '); @@ -159,14 +152,6 @@ class ConventionalCommit { ) .toList(); - final scopes = (match.namedGroup('scope') ?? '') - .replaceAll(RegExp(r'^\('), '') - .replaceAll(RegExp(r'\)$'), '') - .split(',') - .map((e) => e.trim()) - .where((element) => element.isNotEmpty) - .toList(); - return ConventionalCommit._( body: body, breakingChangeDescription: breakingChangeDescription, @@ -186,6 +171,12 @@ class ConventionalCommit { /// The type specified in this commit, e.g. `feat`. final String? type; + /// Whether this commit adds a new feature. + bool get isFeature => type == 'feat'; + + /// Whether this commit represents a bug fix. + bool get isFix => type == 'fix'; + /// Whether this commit was a breaking change, e.g. `!` was specified after /// the scopes in the commit message. final bool isBreakingChange; @@ -222,42 +213,10 @@ class ConventionalCommit { /// also be used as a token. final List footers; - // TODO(Salakar): this api should probably not be in this package - /// Whether this commit should trigger a version bump in it's residing - /// package. - bool get isVersionableCommit { - if (isMergeCommit) return false; - return isBreakingChange || - [ - 'docs', // TODO: what if markdown docs and not code docs - 'feat', - 'fix', - 'bug', - 'perf', - 'refactor', - 'revert', - ].contains(type); - } - - // TODO(Salakar): this api should probably not be in this package - /// Returns the [SemverReleaseType] for this commit, e.g. - /// [SemverReleaseType.major]. - SemverReleaseType get semverReleaseType { - if (isBreakingChange) { - return SemverReleaseType.major; - } - - if (type == 'feat') { - return SemverReleaseType.minor; - } - - return SemverReleaseType.patch; - } - @override String toString() { return ''' -ConventionalCommit[ +ConventionalCommit( type="$type", scopes=$scopes, description="$description", @@ -266,6 +225,6 @@ ConventionalCommit[ isBreakingChange=$isBreakingChange, breakingChangeDescription=$breakingChangeDescription, footers=$footers -]'''; +)'''; } } diff --git a/packages/conventional_commit/test/conventional_commit_test.dart b/packages/conventional_commit/test/conventional_commit_test.dart index f96470ae1..a89f4064c 100644 --- a/packages/conventional_commit/test/conventional_commit_test.dart +++ b/packages/conventional_commit/test/conventional_commit_test.dart @@ -63,7 +63,6 @@ void main() { expect(ConventionalCommit.tryParse(': new feature'), isNull); expect(ConventionalCommit.tryParse(' (): new feature'), isNull); expect(ConventionalCommit.tryParse('feat()'), isNull); - expect(ConventionalCommit.tryParse('custom: new feature'), isNull); }); test('accepts commit messages with or without a space before description', @@ -98,6 +97,7 @@ void main() { expect(conventionalCommit?.type, 'feat'); expect(conventionalCommit?.scopes, ['scope']); expect(conventionalCommit?.description, 'new feature'); + expect(conventionalCommit?.isMergeCommit, true); }, ); @@ -108,8 +108,6 @@ void main() { expect(commit.body, equals('This also fixes an issue something else.')); expect(commit.type, equals('feat')); expect(commit.scopes, equals(['*'])); - expect(commit.isVersionableCommit, isTrue); - expect(commit.semverReleaseType, SemverReleaseType.minor); }); test('header', () { @@ -208,6 +206,10 @@ void main() { ConventionalCommit.tryParse('test(scope)!: foo bar')!.type, equals('test'), ); + expect( + ConventionalCommit.tryParse('FIX: foo bar')!.type, + equals('fix'), + ); }); test('isBreakingChange', () { @@ -361,53 +363,6 @@ void main() { ); }); - test('isVersionableCommit', () { - expect( - ConventionalCommit.tryParse('chore!: foo bar')!.isVersionableCommit, - isTrue, - ); - expect( - ConventionalCommit.tryParse('docs: foo bar')!.isVersionableCommit, - isTrue, - ); - expect( - ConventionalCommit.tryParse('refactor(scope): foo bar')! - .isVersionableCommit, - isTrue, - ); - expect( - ConventionalCommit.tryParse('revert(scope,dope)!: foo bar')! - .isVersionableCommit, - isTrue, - ); - expect( - ConventionalCommit.tryParse('ci(scope,dope): foo bar')! - .isVersionableCommit, - isFalse, - ); - }); - - test('semverReleaseType', () { - expect( - ConventionalCommit.tryParse('chore!: foo bar')!.semverReleaseType, - equals(SemverReleaseType.major), - ); - expect( - ConventionalCommit.tryParse('docs: foo bar')!.semverReleaseType, - equals(SemverReleaseType.patch), - ); - expect( - ConventionalCommit.tryParse('refactor(scope): foo bar')! - .semverReleaseType, - equals(SemverReleaseType.patch), - ); - expect( - ConventionalCommit.tryParse('feat(scope,dope): foo bar')! - .semverReleaseType, - equals(SemverReleaseType.minor), - ); - }); - test('body', () { // With a multi-line/paragraph body. expect(