Skip to content

Commit e2a1389

Browse files
authored
Merge pull request #22 from hyiso/feature/parser-options
feat!: support parser options
2 parents 69d78e6 + 90abe8d commit e2a1389

File tree

6 files changed

+152
-48
lines changed

6 files changed

+152
-48
lines changed

lib/src/lint.dart

+10-3
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,27 @@ import 'parse.dart';
33
import 'rules.dart';
44
import 'types/commit.dart';
55
import 'types/lint.dart';
6+
import 'types/parser.dart';
67
import 'types/rule.dart';
78

89
///
910
/// Lint commit [message] with configured [rules]
1011
///
11-
Future<LintOutcome> lint(String message, Map<String, Rule> rules,
12-
{bool? defaultIgnores, Iterable<String>? ignores}) async {
12+
Future<LintOutcome> lint(
13+
String message,
14+
Map<String, Rule> rules, {
15+
ParserOptions? parserOptions,
16+
bool? defaultIgnores,
17+
Iterable<String>? ignores,
18+
}) async {
1319
/// Found a wildcard match, skip
1420
if (isIgnored(message, defaultIgnores: defaultIgnores, ignores: ignores)) {
1521
return LintOutcome(input: message, valid: true, errors: [], warnings: []);
1622
}
1723

1824
/// Parse the commit message
19-
final commit = message.isEmpty ? Commit.empty() : parse(message);
25+
final commit =
26+
message.isEmpty ? Commit.empty() : parse(message, options: parserOptions);
2027

2128
if (commit.header.isEmpty && commit.body == null && commit.footer == null) {
2229
/// Commit is empty, skip

lib/src/load.dart

+8-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:yaml/yaml.dart';
66

77
import 'types/case.dart';
88
import 'types/commitlint.dart';
9+
import 'types/parser.dart';
910
import 'types/rule.dart';
1011

1112
///
@@ -31,11 +32,14 @@ Future<CommitLint> load(
3132
final rules = yaml?['rules'] as YamlMap?;
3233
final ignores = yaml?['ignores'] as YamlList?;
3334
final defaultIgnores = yaml?['defaultIgnores'] as bool?;
35+
final parser = yaml?['parser'] as YamlMap?;
3436
final config = CommitLint(
35-
rules: rules?.map((key, value) => MapEntry(key, _extractRule(value))) ??
36-
{},
37-
ignores: ignores?.cast(),
38-
defaultIgnores: defaultIgnores);
37+
rules:
38+
rules?.map((key, value) => MapEntry(key, _extractRule(value))) ?? {},
39+
ignores: ignores?.cast(),
40+
defaultIgnores: defaultIgnores,
41+
parser: parser != null ? ParserOptions.fromYaml(parser) : null,
42+
);
3943
if (include != null) {
4044
final upstream = await load(include, directory: file.parent);
4145
return config.inherit(upstream);

lib/src/parse.dart

+25-40
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,14 @@
11
import 'types/commit.dart';
2+
import 'types/parser.dart';
23

34
///
45
/// Parse Commit Message String to Convensional Commit
56
///
6-
7-
final _kHeaderPattern =
8-
RegExp(r'^(?<type>\w*?)(\((?<scope>.*)\))?!?: (?<subject>.+)$');
9-
const _kHeaderCorrespondence = ['type', 'scope', 'subject'];
10-
11-
const _kReferenceActions = [
12-
'close',
13-
'closes',
14-
'closed',
15-
'fix',
16-
'fixes',
17-
'fixed',
18-
'resolve',
19-
'resolves',
20-
'resolved'
21-
];
22-
23-
const _kIssuePrefixes = ['#'];
24-
const _kNoteKeywords = ['BREAKING CHANGE', 'BREAKING-CHANGE'];
25-
final _kMergePattern = RegExp(r'^(Merge|merge)\s(.*)$');
26-
final _kRevertPattern = RegExp(
27-
r'^(?:Revert|revert:)\s"?(?<header>[\s\S]+?)"?\s*This reverts commit (?<hash>\w*)\.');
28-
const _kRevertCorrespondence = ['header', 'hash'];
29-
30-
final _kMentionsPattern = RegExp(r'@([\w-]+)');
31-
32-
Commit parse(String raw) {
7+
Commit parse(
8+
String raw, {
9+
ParserOptions? options,
10+
}) {
11+
options ??= const ParserOptions();
3312
if (raw.trim().isEmpty) {
3413
throw ArgumentError.value(raw, null, 'message raw must have content.');
3514
}
@@ -44,7 +23,7 @@ Commit parse(String raw) {
4423
final rawLines = _trimOffNewlines(raw).split(RegExp(r'\r?\n'));
4524
final lines = _truncateToScissor(rawLines).where(_gpgFilter).toList();
4625
merge = lines.removeAt(0);
47-
final mergeMatch = _kMergePattern.firstMatch(merge);
26+
final mergeMatch = RegExp(options.mergePattern).firstMatch(merge);
4827
if (mergeMatch != null) {
4928
merge = mergeMatch.group(0);
5029
if (lines.isNotEmpty) {
@@ -58,22 +37,27 @@ Commit parse(String raw) {
5837
header = merge;
5938
merge = null;
6039
}
61-
final headerMatch = _kHeaderPattern.firstMatch(header);
40+
final headerMatch = RegExp(options.headerPattern).firstMatch(header);
6241
final headerParts = <String, String?>{};
6342
if (headerMatch != null) {
64-
for (var name in _kHeaderCorrespondence) {
65-
headerParts[name] = headerMatch.namedGroup(name);
43+
for (int i = 0; i < options.headerCorrespondence.length; i++) {
44+
final String key = options.headerCorrespondence[i];
45+
headerParts[key] = headerMatch.group(i + 1);
6646
}
47+
// for (var name in options.headerCorrespondence) {
48+
// headerParts[name] = headerMatch.namedGroup(name);
49+
// }
6750
}
68-
final referencesPattern = _getReferenceRegex(_kReferenceActions);
69-
final referencePartsPattern = _getReferencePartsRegex(_kIssuePrefixes, false);
51+
final referencesPattern = _getReferenceRegex(options.referenceActions);
52+
final referencePartsPattern =
53+
_getReferencePartsRegex(options.issuePrefixes, false);
7054
references.addAll(_getReferences(header,
7155
referencesPattern: referencesPattern,
7256
referencePartsPattern: referencePartsPattern));
7357

7458
bool continueNote = false;
7559
bool isBody = true;
76-
final notesPattern = _getNotesRegex(_kNoteKeywords);
60+
final notesPattern = _getNotesRegex(options.noteKeywords);
7761

7862
/// body or footer
7963
for (var line in lines) {
@@ -118,18 +102,19 @@ Commit parse(String raw) {
118102
}
119103
}
120104

121-
Match? mentionsMatch = _kMentionsPattern.firstMatch(raw);
105+
final mentionsRegex = RegExp(options.mentionsPattern);
106+
Match? mentionsMatch = mentionsRegex.firstMatch(raw);
122107
while (mentionsMatch != null) {
123108
mentions.add(mentionsMatch.group(1)!);
124-
mentionsMatch = _kMentionsPattern.matchAsPrefix(raw, mentionsMatch.end);
109+
mentionsMatch = mentionsRegex.matchAsPrefix(raw, mentionsMatch.end);
125110
}
126111

127112
// does this commit revert any other commit?
128-
final revertMatch = _kRevertPattern.firstMatch(raw);
113+
final revertMatch = RegExp(options.revertPattern).firstMatch(raw);
129114
if (revertMatch != null) {
130115
revert = {};
131-
for (var i = 0; i < _kRevertCorrespondence.length; i++) {
132-
revert[_kRevertCorrespondence[i]] = revertMatch.group(i + 1);
116+
for (var i = 0; i < options.revertCorrespondence.length; i++) {
117+
revert[options.revertCorrespondence[i]] = revertMatch.group(i + 1);
133118
}
134119
}
135120

@@ -141,7 +126,7 @@ Commit parse(String raw) {
141126
merge: merge,
142127
header: header,
143128
type: headerParts['type'],
144-
scopes: headerParts['scope']?.split(RegExp(r'(/|,|\\)')),
129+
scopes: headerParts['scope']?.split(RegExp(r'\/|\\|, ?')),
145130
subject: headerParts['subject'],
146131
body: body != null ? _trimOffNewlines(body) : null,
147132
footer: footer != null ? _trimOffNewlines(footer) : null,

lib/src/types/commitlint.dart

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1+
import 'parser.dart';
12
import 'rule.dart';
23

34
class CommitLint {
4-
CommitLint({this.rules = const {}, this.defaultIgnores, this.ignores});
5+
CommitLint({
6+
this.rules = const {},
7+
this.defaultIgnores,
8+
this.ignores,
9+
this.parser,
10+
});
511

612
final Map<String, Rule> rules;
713

814
final bool? defaultIgnores;
915

1016
final Iterable<String>? ignores;
1117

18+
final ParserOptions? parser;
19+
1220
CommitLint inherit(CommitLint other) {
1321
return CommitLint(
1422
rules: {
@@ -20,6 +28,7 @@ class CommitLint {
2028
...?other.ignores,
2129
...?ignores,
2230
],
31+
parser: parser ?? other.parser,
2332
);
2433
}
2534
}

lib/src/types/parser.dart

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import 'package:yaml/yaml.dart';
2+
3+
const _kHeaderPattern =
4+
r'^(?<type>\w*)(?:\((?<scope>.*)\))?!?: (?<subject>.*)$';
5+
const _kHeaderCorrespondence = ['type', 'scope', 'subject'];
6+
7+
const _kReferenceActions = [
8+
'close',
9+
'closes',
10+
'closed',
11+
'fix',
12+
'fixes',
13+
'fixed',
14+
'resolve',
15+
'resolves',
16+
'resolved'
17+
];
18+
19+
const _kIssuePrefixes = ['#'];
20+
const _kNoteKeywords = ['BREAKING CHANGE', 'BREAKING-CHANGE'];
21+
const _kMergePattern = r'^(Merge|merge)\s(.*)$';
22+
const _kRevertPattern =
23+
r'^(?:Revert|revert:)\s"?(?<header>[\s\S]+?)"?\s*This reverts commit (?<hash>\w*)\.';
24+
const _kRevertCorrespondence = ['header', 'hash'];
25+
26+
const _kMentionsPattern = r'@([\w-]+)';
27+
28+
class ParserOptions {
29+
final List<String> issuePrefixes;
30+
final List<String> noteKeywords;
31+
final List<String> referenceActions;
32+
final String headerPattern;
33+
final List<String> headerCorrespondence;
34+
final String revertPattern;
35+
final List<String> revertCorrespondence;
36+
final String mergePattern;
37+
final String mentionsPattern;
38+
39+
const ParserOptions({
40+
this.issuePrefixes = _kIssuePrefixes,
41+
this.noteKeywords = _kNoteKeywords,
42+
this.referenceActions = _kReferenceActions,
43+
this.headerPattern = _kHeaderPattern,
44+
this.headerCorrespondence = _kHeaderCorrespondence,
45+
this.revertPattern = _kRevertPattern,
46+
this.revertCorrespondence = _kRevertCorrespondence,
47+
this.mergePattern = _kMergePattern,
48+
this.mentionsPattern = _kMentionsPattern,
49+
});
50+
51+
ParserOptions copyWith(ParserOptions? other) {
52+
return ParserOptions(
53+
issuePrefixes: other?.issuePrefixes ?? issuePrefixes,
54+
noteKeywords: other?.noteKeywords ?? noteKeywords,
55+
referenceActions: other?.referenceActions ?? referenceActions,
56+
headerPattern: other?.headerPattern ?? headerPattern,
57+
headerCorrespondence: other?.headerCorrespondence ?? headerCorrespondence,
58+
revertPattern: other?.revertPattern ?? revertPattern,
59+
revertCorrespondence: other?.revertCorrespondence ?? revertCorrespondence,
60+
mergePattern: other?.mergePattern ?? mergePattern,
61+
mentionsPattern: other?.mentionsPattern ?? mentionsPattern,
62+
);
63+
}
64+
65+
static ParserOptions fromYaml(YamlMap yaml) {
66+
return ParserOptions(
67+
issuePrefixes:
68+
List<String>.from(yaml['issuePrefixes'] ?? _kIssuePrefixes),
69+
noteKeywords: List<String>.from(yaml['noteKeywords'] ?? _kNoteKeywords),
70+
referenceActions:
71+
List<String>.from(yaml['referenceActions'] ?? _kReferenceActions),
72+
headerPattern: yaml['headerPattern'] ?? _kHeaderPattern,
73+
headerCorrespondence: List<String>.from(
74+
yaml['headerCorrespondence'] ?? _kHeaderCorrespondence),
75+
revertPattern: yaml['revertPattern'] ?? _kRevertPattern,
76+
revertCorrespondence: List<String>.from(
77+
yaml['revertCorrespondence'] ?? _kRevertCorrespondence),
78+
mergePattern: yaml['mergePattern'] ?? _kMergePattern,
79+
mentionsPattern: yaml['mentionsPattern'] ?? _kMentionsPattern,
80+
);
81+
}
82+
}

test/parse_test.dart

+17
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import 'package:collection/collection.dart';
44
import 'package:commitlint_cli/src/parse.dart';
55
import 'package:commitlint_cli/src/types/commit.dart';
6+
import 'package:commitlint_cli/src/types/parser.dart';
67
import 'package:test/test.dart';
78

89
void main() {
@@ -255,6 +256,22 @@ void main() {
255256
expect(commit.body, equals('this is some body before a scissors-line'));
256257
});
257258

259+
test('should use custom parser options with headerPattern', () {
260+
final commit = parse('type(scope)-subject',
261+
options: ParserOptions(headerPattern: r'^(\w*)(?:\((.*)\))?-(.*)$'));
262+
expect(commit.header, equals('type(scope)-subject'));
263+
expect(commit.scopes, equals(['scope']));
264+
expect(commit.subject, equals('subject'));
265+
expect(commit.type, equals('type'));
266+
});
267+
268+
test('should use custom parser options with custom issuePrefixes', () {
269+
final commit = parse('fix: change git convention to fix CD workflow sv-4',
270+
options: ParserOptions(issuePrefixes: ['sv-']));
271+
expect(commit.type, equals('fix'));
272+
expect(commit.references.first.issue, equals('4'));
273+
});
274+
258275
group('merge commits', () {
259276
final githubCommit = parse(
260277
'Merge pull request #1 from user/feature/feature-name\n' +

0 commit comments

Comments
 (0)