Skip to content

Commit

Permalink
Merge pull request #22 from hyiso/feature/parser-options
Browse files Browse the repository at this point in the history
feat!: support parser options
  • Loading branch information
hyiso authored Nov 11, 2024
2 parents 69d78e6 + 90abe8d commit e2a1389
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 48 deletions.
13 changes: 10 additions & 3 deletions lib/src/lint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,27 @@ import 'parse.dart';
import 'rules.dart';
import 'types/commit.dart';
import 'types/lint.dart';
import 'types/parser.dart';
import 'types/rule.dart';

///
/// Lint commit [message] with configured [rules]
///
Future<LintOutcome> lint(String message, Map<String, Rule> rules,
{bool? defaultIgnores, Iterable<String>? ignores}) async {
Future<LintOutcome> lint(
String message,
Map<String, Rule> rules, {
ParserOptions? parserOptions,
bool? defaultIgnores,
Iterable<String>? ignores,
}) async {
/// Found a wildcard match, skip
if (isIgnored(message, defaultIgnores: defaultIgnores, ignores: ignores)) {
return LintOutcome(input: message, valid: true, errors: [], warnings: []);
}

/// Parse the commit message
final commit = message.isEmpty ? Commit.empty() : parse(message);
final commit =
message.isEmpty ? Commit.empty() : parse(message, options: parserOptions);

if (commit.header.isEmpty && commit.body == null && commit.footer == null) {
/// Commit is empty, skip
Expand Down
12 changes: 8 additions & 4 deletions lib/src/load.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:yaml/yaml.dart';

import 'types/case.dart';
import 'types/commitlint.dart';
import 'types/parser.dart';
import 'types/rule.dart';

///
Expand All @@ -31,11 +32,14 @@ Future<CommitLint> load(
final rules = yaml?['rules'] as YamlMap?;
final ignores = yaml?['ignores'] as YamlList?;
final defaultIgnores = yaml?['defaultIgnores'] as bool?;
final parser = yaml?['parser'] as YamlMap?;
final config = CommitLint(
rules: rules?.map((key, value) => MapEntry(key, _extractRule(value))) ??
{},
ignores: ignores?.cast(),
defaultIgnores: defaultIgnores);
rules:
rules?.map((key, value) => MapEntry(key, _extractRule(value))) ?? {},
ignores: ignores?.cast(),
defaultIgnores: defaultIgnores,
parser: parser != null ? ParserOptions.fromYaml(parser) : null,
);
if (include != null) {
final upstream = await load(include, directory: file.parent);
return config.inherit(upstream);
Expand Down
65 changes: 25 additions & 40 deletions lib/src/parse.dart
Original file line number Diff line number Diff line change
@@ -1,35 +1,14 @@
import 'types/commit.dart';
import 'types/parser.dart';

///
/// Parse Commit Message String to Convensional Commit
///
final _kHeaderPattern =
RegExp(r'^(?<type>\w*?)(\((?<scope>.*)\))?!?: (?<subject>.+)$');
const _kHeaderCorrespondence = ['type', 'scope', 'subject'];

const _kReferenceActions = [
'close',
'closes',
'closed',
'fix',
'fixes',
'fixed',
'resolve',
'resolves',
'resolved'
];

const _kIssuePrefixes = ['#'];
const _kNoteKeywords = ['BREAKING CHANGE', 'BREAKING-CHANGE'];
final _kMergePattern = RegExp(r'^(Merge|merge)\s(.*)$');
final _kRevertPattern = RegExp(
r'^(?:Revert|revert:)\s"?(?<header>[\s\S]+?)"?\s*This reverts commit (?<hash>\w*)\.');
const _kRevertCorrespondence = ['header', 'hash'];

final _kMentionsPattern = RegExp(r'@([\w-]+)');

Commit parse(String raw) {
Commit parse(
String raw, {
ParserOptions? options,
}) {
options ??= const ParserOptions();
if (raw.trim().isEmpty) {
throw ArgumentError.value(raw, null, 'message raw must have content.');
}
Expand All @@ -44,7 +23,7 @@ Commit parse(String raw) {
final rawLines = _trimOffNewlines(raw).split(RegExp(r'\r?\n'));
final lines = _truncateToScissor(rawLines).where(_gpgFilter).toList();
merge = lines.removeAt(0);
final mergeMatch = _kMergePattern.firstMatch(merge);
final mergeMatch = RegExp(options.mergePattern).firstMatch(merge);
if (mergeMatch != null) {
merge = mergeMatch.group(0);
if (lines.isNotEmpty) {
Expand All @@ -58,22 +37,27 @@ Commit parse(String raw) {
header = merge;
merge = null;
}
final headerMatch = _kHeaderPattern.firstMatch(header);
final headerMatch = RegExp(options.headerPattern).firstMatch(header);
final headerParts = <String, String?>{};
if (headerMatch != null) {
for (var name in _kHeaderCorrespondence) {
headerParts[name] = headerMatch.namedGroup(name);
for (int i = 0; i < options.headerCorrespondence.length; i++) {
final String key = options.headerCorrespondence[i];
headerParts[key] = headerMatch.group(i + 1);
}
// for (var name in options.headerCorrespondence) {
// headerParts[name] = headerMatch.namedGroup(name);
// }
}
final referencesPattern = _getReferenceRegex(_kReferenceActions);
final referencePartsPattern = _getReferencePartsRegex(_kIssuePrefixes, false);
final referencesPattern = _getReferenceRegex(options.referenceActions);
final referencePartsPattern =
_getReferencePartsRegex(options.issuePrefixes, false);
references.addAll(_getReferences(header,
referencesPattern: referencesPattern,
referencePartsPattern: referencePartsPattern));

bool continueNote = false;
bool isBody = true;
final notesPattern = _getNotesRegex(_kNoteKeywords);
final notesPattern = _getNotesRegex(options.noteKeywords);

/// body or footer
for (var line in lines) {
Expand Down Expand Up @@ -118,18 +102,19 @@ Commit parse(String raw) {
}
}

Match? mentionsMatch = _kMentionsPattern.firstMatch(raw);
final mentionsRegex = RegExp(options.mentionsPattern);
Match? mentionsMatch = mentionsRegex.firstMatch(raw);
while (mentionsMatch != null) {
mentions.add(mentionsMatch.group(1)!);
mentionsMatch = _kMentionsPattern.matchAsPrefix(raw, mentionsMatch.end);
mentionsMatch = mentionsRegex.matchAsPrefix(raw, mentionsMatch.end);
}

// does this commit revert any other commit?
final revertMatch = _kRevertPattern.firstMatch(raw);
final revertMatch = RegExp(options.revertPattern).firstMatch(raw);
if (revertMatch != null) {
revert = {};
for (var i = 0; i < _kRevertCorrespondence.length; i++) {
revert[_kRevertCorrespondence[i]] = revertMatch.group(i + 1);
for (var i = 0; i < options.revertCorrespondence.length; i++) {
revert[options.revertCorrespondence[i]] = revertMatch.group(i + 1);
}
}

Expand All @@ -141,7 +126,7 @@ Commit parse(String raw) {
merge: merge,
header: header,
type: headerParts['type'],
scopes: headerParts['scope']?.split(RegExp(r'(/|,|\\)')),
scopes: headerParts['scope']?.split(RegExp(r'\/|\\|, ?')),
subject: headerParts['subject'],
body: body != null ? _trimOffNewlines(body) : null,
footer: footer != null ? _trimOffNewlines(footer) : null,
Expand Down
11 changes: 10 additions & 1 deletion lib/src/types/commitlint.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import 'parser.dart';
import 'rule.dart';

class CommitLint {
CommitLint({this.rules = const {}, this.defaultIgnores, this.ignores});
CommitLint({
this.rules = const {},
this.defaultIgnores,
this.ignores,
this.parser,
});

final Map<String, Rule> rules;

final bool? defaultIgnores;

final Iterable<String>? ignores;

final ParserOptions? parser;

CommitLint inherit(CommitLint other) {
return CommitLint(
rules: {
Expand All @@ -20,6 +28,7 @@ class CommitLint {
...?other.ignores,
...?ignores,
],
parser: parser ?? other.parser,
);
}
}
82 changes: 82 additions & 0 deletions lib/src/types/parser.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'package:yaml/yaml.dart';

const _kHeaderPattern =
r'^(?<type>\w*)(?:\((?<scope>.*)\))?!?: (?<subject>.*)$';
const _kHeaderCorrespondence = ['type', 'scope', 'subject'];

const _kReferenceActions = [
'close',
'closes',
'closed',
'fix',
'fixes',
'fixed',
'resolve',
'resolves',
'resolved'
];

const _kIssuePrefixes = ['#'];
const _kNoteKeywords = ['BREAKING CHANGE', 'BREAKING-CHANGE'];
const _kMergePattern = r'^(Merge|merge)\s(.*)$';
const _kRevertPattern =
r'^(?:Revert|revert:)\s"?(?<header>[\s\S]+?)"?\s*This reverts commit (?<hash>\w*)\.';
const _kRevertCorrespondence = ['header', 'hash'];

const _kMentionsPattern = r'@([\w-]+)';

class ParserOptions {
final List<String> issuePrefixes;
final List<String> noteKeywords;
final List<String> referenceActions;
final String headerPattern;
final List<String> headerCorrespondence;
final String revertPattern;
final List<String> revertCorrespondence;
final String mergePattern;
final String mentionsPattern;

const ParserOptions({
this.issuePrefixes = _kIssuePrefixes,
this.noteKeywords = _kNoteKeywords,
this.referenceActions = _kReferenceActions,
this.headerPattern = _kHeaderPattern,
this.headerCorrespondence = _kHeaderCorrespondence,
this.revertPattern = _kRevertPattern,
this.revertCorrespondence = _kRevertCorrespondence,
this.mergePattern = _kMergePattern,
this.mentionsPattern = _kMentionsPattern,
});

ParserOptions copyWith(ParserOptions? other) {
return ParserOptions(
issuePrefixes: other?.issuePrefixes ?? issuePrefixes,
noteKeywords: other?.noteKeywords ?? noteKeywords,
referenceActions: other?.referenceActions ?? referenceActions,
headerPattern: other?.headerPattern ?? headerPattern,
headerCorrespondence: other?.headerCorrespondence ?? headerCorrespondence,
revertPattern: other?.revertPattern ?? revertPattern,
revertCorrespondence: other?.revertCorrespondence ?? revertCorrespondence,
mergePattern: other?.mergePattern ?? mergePattern,
mentionsPattern: other?.mentionsPattern ?? mentionsPattern,
);
}

static ParserOptions fromYaml(YamlMap yaml) {
return ParserOptions(
issuePrefixes:
List<String>.from(yaml['issuePrefixes'] ?? _kIssuePrefixes),
noteKeywords: List<String>.from(yaml['noteKeywords'] ?? _kNoteKeywords),
referenceActions:
List<String>.from(yaml['referenceActions'] ?? _kReferenceActions),
headerPattern: yaml['headerPattern'] ?? _kHeaderPattern,
headerCorrespondence: List<String>.from(
yaml['headerCorrespondence'] ?? _kHeaderCorrespondence),
revertPattern: yaml['revertPattern'] ?? _kRevertPattern,
revertCorrespondence: List<String>.from(
yaml['revertCorrespondence'] ?? _kRevertCorrespondence),
mergePattern: yaml['mergePattern'] ?? _kMergePattern,
mentionsPattern: yaml['mentionsPattern'] ?? _kMentionsPattern,
);
}
}
17 changes: 17 additions & 0 deletions test/parse_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import 'package:collection/collection.dart';
import 'package:commitlint_cli/src/parse.dart';
import 'package:commitlint_cli/src/types/commit.dart';
import 'package:commitlint_cli/src/types/parser.dart';
import 'package:test/test.dart';

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

test('should use custom parser options with headerPattern', () {
final commit = parse('type(scope)-subject',
options: ParserOptions(headerPattern: r'^(\w*)(?:\((.*)\))?-(.*)$'));
expect(commit.header, equals('type(scope)-subject'));
expect(commit.scopes, equals(['scope']));
expect(commit.subject, equals('subject'));
expect(commit.type, equals('type'));
});

test('should use custom parser options with custom issuePrefixes', () {
final commit = parse('fix: change git convention to fix CD workflow sv-4',
options: ParserOptions(issuePrefixes: ['sv-']));
expect(commit.type, equals('fix'));
expect(commit.references.first.issue, equals('4'));
});

group('merge commits', () {
final githubCommit = parse(
'Merge pull request #1 from user/feature/feature-name\n' +
Expand Down

0 comments on commit e2a1389

Please sign in to comment.