Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support ignores commit message #12

Merged
merged 3 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,28 @@ dart pub add --dev commitlint_cli
### Configuration

```bash
# Configure commitlint to use conventional config
# Simply use configuration of a package
echo "include: package:commitlint_cli/commitlint.yaml" > commitlint.yaml
```
You can also customize your configuration in `commitlint.yaml`
```yaml
# Inherit configuration of a package
include: package:commitlint_cli/commitlint.yaml

# Custom rules
rules:
type-case:
- 2
- always
- 'upper-case'

# Whether commitlint uses the default ignore rules.
defaultIgnores: true
# Pattern that matches commit message if commitlint should ignore the given message.
ignores:
- r'^fixup'
```


### Test

Expand Down
22 changes: 22 additions & 0 deletions lib/src/is_ignored.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
bool isIgnored(String message,
{bool? defaultIgnores, Iterable<String>? ignores}) {
final base = defaultIgnores == false ? [] : wildcards;
return [...base, ...?ignores?.map(ignore)].any((mathcer) => mathcer(message));
}

final wildcards = [
ignore(
r'((Merge pull request)|(Merge (.*?) into (.*?)|(Merge branch (.*?)))(?:\r?\n)*$)'),
ignore(r'(Merge tag (.*?))(?:\r?\n)*$'),
ignore(r'(R|r)evert (.*)'),
ignore(r'(fixup|squash)!'),
ignore(r'(Merged (.*?)(in|into) (.*)|Merged PR (.*): (.*))'),
ignore(r'Merge remote-tracking branch(\s*)(.*)'),
ignore(r'Automatic merge(.*)'),
ignore(r'Auto-merged (.*?) into (.*)'),
];

Matcher ignore(String pattern) =>
(String message) => RegExp(pattern).hasMatch(message);

typedef Matcher = bool Function(String);
21 changes: 14 additions & 7 deletions lib/src/lint.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'is_ignored.dart';
import 'parse.dart';
import 'rules.dart';
import 'types/commit.dart';
Expand All @@ -7,12 +8,18 @@ import 'types/rule.dart';
///
/// Lint commit [message] with configured [rules]
///
Future<LintOutcome> lint(String message, Map<String, RuleConfig> rules) async {
// Parse the commit message
Future<LintOutcome> lint(String message, Map<String, Rule> rules,
{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);

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

/// Validate against all rules
final results = rules.entries
// Level 0 rules are ignored
.where((entry) => entry.value.severity != RuleConfigSeverity.ignore)
.where((entry) => entry.value.severity != RuleSeverity.ignore)
.map((entry) {
final name = entry.key;
final config = entry.value;
Expand All @@ -49,9 +56,9 @@ Future<LintOutcome> lint(String message, Map<String, RuleConfig> rules) async {
.where((outcome) => !outcome.valid)
.toList();
final errors =
results.where((element) => element.level == RuleConfigSeverity.error);
results.where((element) => element.level == RuleSeverity.error);
final warnings =
results.where((element) => element.level == RuleConfigSeverity.warning);
results.where((element) => element.level == RuleSeverity.warning);
return LintOutcome(
input: message,
valid: errors.isEmpty,
Expand Down
93 changes: 45 additions & 48 deletions lib/src/load.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,117 +5,114 @@ import 'package:path/path.dart';
import 'package:yaml/yaml.dart';

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

///
/// Load configured rules in given [file] from given [dir].
/// Load configured rules in given [path] from given [directory].
///
Future<Map<String, RuleConfig>> load({
required String file,
String? dir,
Future<CommitLint> load(
String path, {
Directory? directory,
}) async {
Map<String, RuleConfig> rules = {};
Uri? uri;
if (!file.startsWith('package:')) {
uri = toUri(join(dir ?? Directory.current.path, file));
dir = dirname(uri.path);
File? file;
if (!path.startsWith('package:')) {
final uri = toUri(join(directory?.path ?? Directory.current.path, path));
file = File.fromUri(uri);
} else {
uri = await Isolate.resolvePackageUri(Uri.parse(file));
dir = uri?.path.split('/lib/').first;
final uri = await Isolate.resolvePackageUri(Uri.parse(path));
if (uri != null) {
file = File.fromUri(uri);
}
}
if (uri != null) {
final file = File.fromUri(uri);
if (await file.exists()) {
final yaml = loadYaml(await file.readAsString());
final include = yaml?['include'] as String?;
final rulesMap = yaml?['rules'] as Map?;
if (rulesMap != null) {
for (var entry in rulesMap.entries) {
rules[entry.key] = _extractRuleConfig(entry.value);
}
}
if (include != null) {
final upstream = await load(dir: dir, file: include);
if (upstream.isNotEmpty) {
rules = {
...upstream,
...rules,
};
}
}
if (file != null && file.existsSync()) {
final yaml = loadYaml(await file.readAsString());
final include = yaml?['include'] as String?;
final rules = yaml?['rules'] as YamlMap?;
final ignores = yaml?['ignores'] as YamlList?;
final defaultIgnores = yaml?['defaultIgnores'] as bool?;
final config = CommitLint(
rules: rules?.map((key, value) => MapEntry(key, _extractRule(value))) ??
{},
ignores: ignores?.cast(),
defaultIgnores: defaultIgnores);
if (include != null) {
final upstream = await load(include, directory: file.parent);
return config.inherit(upstream);
}
return config;
}
return rules;
return CommitLint();
}

RuleConfig _extractRuleConfig(dynamic config) {
Rule _extractRule(dynamic config) {
if (config is! List) {
throw Exception('rule config must be list, but get $config');
}
if (config.isEmpty || config.length < 2 || config.length > 3) {
throw Exception(
'rule config must contain at least two, at most three items.');
}
final severity = _extractRuleConfigSeverity(config.first as int);
final condition = _extractRuleConfigCondition(config.elementAt(1) as String);
final severity = _extractRuleSeverity(config.first as int);
final condition = _extractRuleCondition(config.elementAt(1) as String);
dynamic value;
if (config.length == 3) {
value = config.last;
}
if (value == null) {
return RuleConfig(severity: severity, condition: condition);
return Rule(severity: severity, condition: condition);
}
if (value is num) {
return LengthRuleConfig(
return LengthRule(
severity: severity,
condition: condition,
length: value,
);
}
if (value is String) {
if (value.endsWith('-case')) {
return CaseRuleConfig(
return CaseRule(
severity: severity,
condition: condition,
type: _extractCase(value),
);
} else {
return ValueRuleConfig(
return ValueRule(
severity: severity,
condition: condition,
value: value,
);
}
}
if (value is List) {
return EnumRuleConfig(
return EnumRule(
severity: severity,
condition: condition,
allowed: value.cast(),
);
}
return ValueRuleConfig(
return ValueRule(
severity: severity,
condition: condition,
value: value,
);
}

RuleConfigSeverity _extractRuleConfigSeverity(int severity) {
if (severity < 0 || severity > RuleConfigSeverity.values.length - 1) {
RuleSeverity _extractRuleSeverity(int severity) {
if (severity < 0 || severity > RuleSeverity.values.length - 1) {
throw Exception(
'rule severity can only be 0..${RuleConfigSeverity.values.length - 1}');
'rule severity can only be 0..${RuleSeverity.values.length - 1}');
}
return RuleConfigSeverity.values[severity];
return RuleSeverity.values[severity];
}

RuleConfigCondition _extractRuleConfigCondition(String condition) {
var allowed = RuleConfigCondition.values.map((e) => e.name).toList();
RuleCondition _extractRuleCondition(String condition) {
var allowed = RuleCondition.values.map((e) => e.name).toList();
final index = allowed.indexOf(condition);
if (index == -1) {
throw Exception('rule condition can only one of $allowed');
}
return RuleConfigCondition.values[index];
return RuleCondition.values[index];
}

Case _extractCase(String name) {
Expand Down
Loading