Skip to content

Commit 6ca0b5a

Browse files
authored
Merge pull request #12 from hyiso/feature/ignores
feat: support ignores commit message
2 parents d6fada9 + c7210a2 commit 6ca0b5a

File tree

14 files changed

+273
-157
lines changed

14 files changed

+273
-157
lines changed

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,28 @@ dart pub add --dev commitlint_cli
3030
### Configuration
3131

3232
```bash
33-
# Configure commitlint to use conventional config
33+
# Simply use configuration of a package
3434
echo "include: package:commitlint_cli/commitlint.yaml" > commitlint.yaml
3535
```
36+
You can also customize your configuration in `commitlint.yaml`
37+
```yaml
38+
# Inherit configuration of a package
39+
include: package:commitlint_cli/commitlint.yaml
40+
41+
# Custom rules
42+
rules:
43+
type-case:
44+
- 2
45+
- always
46+
- 'upper-case'
47+
48+
# Whether commitlint uses the default ignore rules.
49+
defaultIgnores: true
50+
# Pattern that matches commit message if commitlint should ignore the given message.
51+
ignores:
52+
- r'^fixup'
53+
```
54+
3655
3756
### Test
3857

lib/src/is_ignored.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
bool isIgnored(String message,
2+
{bool? defaultIgnores, Iterable<String>? ignores}) {
3+
final base = defaultIgnores == false ? [] : wildcards;
4+
return [...base, ...?ignores?.map(ignore)].any((mathcer) => mathcer(message));
5+
}
6+
7+
final wildcards = [
8+
ignore(
9+
r'((Merge pull request)|(Merge (.*?) into (.*?)|(Merge branch (.*?)))(?:\r?\n)*$)'),
10+
ignore(r'(Merge tag (.*?))(?:\r?\n)*$'),
11+
ignore(r'(R|r)evert (.*)'),
12+
ignore(r'(fixup|squash)!'),
13+
ignore(r'(Merged (.*?)(in|into) (.*)|Merged PR (.*): (.*))'),
14+
ignore(r'Merge remote-tracking branch(\s*)(.*)'),
15+
ignore(r'Automatic merge(.*)'),
16+
ignore(r'Auto-merged (.*?) into (.*)'),
17+
];
18+
19+
Matcher ignore(String pattern) =>
20+
(String message) => RegExp(pattern).hasMatch(message);
21+
22+
typedef Matcher = bool Function(String);

lib/src/lint.dart

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'is_ignored.dart';
12
import 'parse.dart';
23
import 'rules.dart';
34
import 'types/commit.dart';
@@ -7,12 +8,18 @@ import 'types/rule.dart';
78
///
89
/// Lint commit [message] with configured [rules]
910
///
10-
Future<LintOutcome> lint(String message, Map<String, RuleConfig> rules) async {
11-
// Parse the commit message
11+
Future<LintOutcome> lint(String message, Map<String, Rule> rules,
12+
{bool? defaultIgnores, Iterable<String>? ignores}) async {
13+
/// Found a wildcard match, skip
14+
if (isIgnored(message, defaultIgnores: defaultIgnores, ignores: ignores)) {
15+
return LintOutcome(input: message, valid: true, errors: [], warnings: []);
16+
}
17+
18+
/// Parse the commit message
1219
final commit = message.isEmpty ? Commit.empty() : parse(message);
1320

1421
if (commit.header.isEmpty && commit.body == null && commit.footer == null) {
15-
// Commit is empty, skip
22+
/// Commit is empty, skip
1623
return LintOutcome(input: message, valid: true, errors: [], warnings: []);
1724
}
1825
final allRules = Map.of(supportedRules);
@@ -22,13 +29,13 @@ Future<LintOutcome> lint(String message, Map<String, RuleConfig> rules) async {
2229
if (missing.isNotEmpty) {
2330
final names = [...allRules.keys];
2431
throw RangeError(
25-
'Found invalid rule names: ${missing.join(', ')}. Supported rule names are: ${names.join(', ')}');
32+
'Found invalid rule names: ${missing.join(', ')}. \nSupported rule names are: ${names.join(', ')}');
2633
}
2734

2835
/// Validate against all rules
2936
final results = rules.entries
3037
// Level 0 rules are ignored
31-
.where((entry) => entry.value.severity != RuleConfigSeverity.ignore)
38+
.where((entry) => entry.value.severity != RuleSeverity.ignore)
3239
.map((entry) {
3340
final name = entry.key;
3441
final config = entry.value;
@@ -49,9 +56,9 @@ Future<LintOutcome> lint(String message, Map<String, RuleConfig> rules) async {
4956
.where((outcome) => !outcome.valid)
5057
.toList();
5158
final errors =
52-
results.where((element) => element.level == RuleConfigSeverity.error);
59+
results.where((element) => element.level == RuleSeverity.error);
5360
final warnings =
54-
results.where((element) => element.level == RuleConfigSeverity.warning);
61+
results.where((element) => element.level == RuleSeverity.warning);
5562
return LintOutcome(
5663
input: message,
5764
valid: errors.isEmpty,

lib/src/load.dart

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,117 +5,114 @@ import 'package:path/path.dart';
55
import 'package:yaml/yaml.dart';
66

77
import 'types/case.dart';
8+
import 'types/commitlint.dart';
89
import 'types/rule.dart';
910

1011
///
11-
/// Load configured rules in given [file] from given [dir].
12+
/// Load configured rules in given [path] from given [directory].
1213
///
13-
Future<Map<String, RuleConfig>> load({
14-
required String file,
15-
String? dir,
14+
Future<CommitLint> load(
15+
String path, {
16+
Directory? directory,
1617
}) async {
17-
Map<String, RuleConfig> rules = {};
18-
Uri? uri;
19-
if (!file.startsWith('package:')) {
20-
uri = toUri(join(dir ?? Directory.current.path, file));
21-
dir = dirname(uri.path);
18+
File? file;
19+
if (!path.startsWith('package:')) {
20+
final uri = toUri(join(directory?.path ?? Directory.current.path, path));
21+
file = File.fromUri(uri);
2222
} else {
23-
uri = await Isolate.resolvePackageUri(Uri.parse(file));
24-
dir = uri?.path.split('/lib/').first;
23+
final uri = await Isolate.resolvePackageUri(Uri.parse(path));
24+
if (uri != null) {
25+
file = File.fromUri(uri);
26+
}
2527
}
26-
if (uri != null) {
27-
final file = File.fromUri(uri);
28-
if (await file.exists()) {
29-
final yaml = loadYaml(await file.readAsString());
30-
final include = yaml?['include'] as String?;
31-
final rulesMap = yaml?['rules'] as Map?;
32-
if (rulesMap != null) {
33-
for (var entry in rulesMap.entries) {
34-
rules[entry.key] = _extractRuleConfig(entry.value);
35-
}
36-
}
37-
if (include != null) {
38-
final upstream = await load(dir: dir, file: include);
39-
if (upstream.isNotEmpty) {
40-
rules = {
41-
...upstream,
42-
...rules,
43-
};
44-
}
45-
}
28+
if (file != null && file.existsSync()) {
29+
final yaml = loadYaml(await file.readAsString());
30+
final include = yaml?['include'] as String?;
31+
final rules = yaml?['rules'] as YamlMap?;
32+
final ignores = yaml?['ignores'] as YamlList?;
33+
final defaultIgnores = yaml?['defaultIgnores'] as bool?;
34+
final config = CommitLint(
35+
rules: rules?.map((key, value) => MapEntry(key, _extractRule(value))) ??
36+
{},
37+
ignores: ignores?.cast(),
38+
defaultIgnores: defaultIgnores);
39+
if (include != null) {
40+
final upstream = await load(include, directory: file.parent);
41+
return config.inherit(upstream);
4642
}
43+
return config;
4744
}
48-
return rules;
45+
return CommitLint();
4946
}
5047

51-
RuleConfig _extractRuleConfig(dynamic config) {
48+
Rule _extractRule(dynamic config) {
5249
if (config is! List) {
5350
throw Exception('rule config must be list, but get $config');
5451
}
5552
if (config.isEmpty || config.length < 2 || config.length > 3) {
5653
throw Exception(
5754
'rule config must contain at least two, at most three items.');
5855
}
59-
final severity = _extractRuleConfigSeverity(config.first as int);
60-
final condition = _extractRuleConfigCondition(config.elementAt(1) as String);
56+
final severity = _extractRuleSeverity(config.first as int);
57+
final condition = _extractRuleCondition(config.elementAt(1) as String);
6158
dynamic value;
6259
if (config.length == 3) {
6360
value = config.last;
6461
}
6562
if (value == null) {
66-
return RuleConfig(severity: severity, condition: condition);
63+
return Rule(severity: severity, condition: condition);
6764
}
6865
if (value is num) {
69-
return LengthRuleConfig(
66+
return LengthRule(
7067
severity: severity,
7168
condition: condition,
7269
length: value,
7370
);
7471
}
7572
if (value is String) {
7673
if (value.endsWith('-case')) {
77-
return CaseRuleConfig(
74+
return CaseRule(
7875
severity: severity,
7976
condition: condition,
8077
type: _extractCase(value),
8178
);
8279
} else {
83-
return ValueRuleConfig(
80+
return ValueRule(
8481
severity: severity,
8582
condition: condition,
8683
value: value,
8784
);
8885
}
8986
}
9087
if (value is List) {
91-
return EnumRuleConfig(
88+
return EnumRule(
9289
severity: severity,
9390
condition: condition,
9491
allowed: value.cast(),
9592
);
9693
}
97-
return ValueRuleConfig(
94+
return ValueRule(
9895
severity: severity,
9996
condition: condition,
10097
value: value,
10198
);
10299
}
103100

104-
RuleConfigSeverity _extractRuleConfigSeverity(int severity) {
105-
if (severity < 0 || severity > RuleConfigSeverity.values.length - 1) {
101+
RuleSeverity _extractRuleSeverity(int severity) {
102+
if (severity < 0 || severity > RuleSeverity.values.length - 1) {
106103
throw Exception(
107-
'rule severity can only be 0..${RuleConfigSeverity.values.length - 1}');
104+
'rule severity can only be 0..${RuleSeverity.values.length - 1}');
108105
}
109-
return RuleConfigSeverity.values[severity];
106+
return RuleSeverity.values[severity];
110107
}
111108

112-
RuleConfigCondition _extractRuleConfigCondition(String condition) {
113-
var allowed = RuleConfigCondition.values.map((e) => e.name).toList();
109+
RuleCondition _extractRuleCondition(String condition) {
110+
var allowed = RuleCondition.values.map((e) => e.name).toList();
114111
final index = allowed.indexOf(condition);
115112
if (index == -1) {
116113
throw Exception('rule condition can only one of $allowed');
117114
}
118-
return RuleConfigCondition.values[index];
115+
return RuleCondition.values[index];
119116
}
120117

121118
Case _extractCase(String name) {

0 commit comments

Comments
 (0)