Skip to content

Commit afba942

Browse files
authored
[Feature] Title conventional with task (#61)
* add group for message * add group for type and module * Add task number * check any project code * allow many project codes * use arrayee * up test * update docs * clean rule * fix lint * fix stat analyse * Add ProjectCode helper * up version * Add test for ProjectCode helper
1 parent e5b8b22 commit afba942

File tree

15 files changed

+312
-88
lines changed

15 files changed

+312
-88
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ This file contains changelogs.
66

77
-----------------------------------------------------------------
88

9+
## [v0.17.1 (2023-09-02)](https://github.com/ArtARTs36/php-merge-request-linter/compare/0.17.0..0.17.1)
10+
11+
### Added
12+
* Added param `task` to `title_conventional` rule for checking if title contains task number
13+
14+
[💾 Assets](https://github.com/ArtARTs36/php-merge-request-linter/releases/tag/0.17.1)
15+
16+
-----------------------------------------------------------------
17+
918
## [v0.17.0 (2023-08-30)](https://github.com/ArtARTs36/php-merge-request-linter/compare/0.16.4..0.17.0)
1019

1120
### Optimized

docs/rules.md

+1
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,4 @@ The title must match conventional commit pattern https://www.conventionalcommits
174174
| Name | Description | Type | Examples |
175175
| ------------ | ------------ |------ | ------|
176176
| types | Commit types | array of strings | "build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test" |
177+
| task | Check if title contains task number | object | |

mr-linter-config-schema.json

+16
Original file line numberDiff line numberDiff line change
@@ -6623,6 +6623,22 @@
66236623
]
66246624
}
66256625
]
6626+
},
6627+
"task": {
6628+
"type": "object",
6629+
"description": "Check if title contains task number",
6630+
"properties": {
6631+
"projectCodes": {
6632+
"type": "array",
6633+
"description": "Project codes. Empty list allowed for any projects",
6634+
"items": [
6635+
{
6636+
"type": "string",
6637+
"description": "Project codes. Empty list allowed for any projects"
6638+
}
6639+
]
6640+
}
6641+
}
66266642
}
66276643
},
66286644
"additionalProperties": false
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace ArtARTs36\MergeRequestLinter\Application\Rule\Regex;
4+
5+
use ArtARTs36\Str\Str;
6+
7+
class ProjectCode
8+
{
9+
/**
10+
* Find project code in string start.
11+
*/
12+
public static function findInStart(Str $str): ?Str
13+
{
14+
$code = $str->match("/^(\w+)-\d+/");
15+
16+
return $code->isEmpty() ? null : $code;
17+
}
18+
}

src/Application/Rule/Rules/CompositeRule.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ private function __construct(
2424
public static function make(array $rules): self
2525
{
2626
if (count($rules) === 0) {
27-
throw new \InvalidArgumentException(sprintf('Argument "rules" must not be empty'));
27+
throw new \InvalidArgumentException('Argument "rules" must not be empty');
2828
}
2929

3030
return new self($rules);

src/Application/Rule/Rules/DefaultRules.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
namespace ArtARTs36\MergeRequestLinter\Application\Rule\Rules;
44

5-
use ArtARTs36\MergeRequestLinter\Shared\DataStructure\ArrayMap;
5+
use ArtARTs36\MergeRequestLinter\Application\Rule\Rules\TitleConventionalRule\TitleConventionalRule;
66
use ArtARTs36\MergeRequestLinter\Domain\Rule\Rule;
7+
use ArtARTs36\MergeRequestLinter\Shared\DataStructure\ArrayMap;
78

89
/**
910
* @codeCoverageIgnore

src/Application/Rule/Rules/TitleConventionalRule.php renamed to src/Application/Rule/Rules/TitleConventionalRule/TitleConventionalRule.php

+56-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
<?php
22

3-
namespace ArtARTs36\MergeRequestLinter\Application\Rule\Rules;
3+
namespace ArtARTs36\MergeRequestLinter\Application\Rule\Rules\TitleConventionalRule;
44

55
use ArtARTs36\MergeRequestLinter\Application\Rule\Definition\Definition;
6+
use ArtARTs36\MergeRequestLinter\Application\Rule\Regex\ProjectCode;
7+
use ArtARTs36\MergeRequestLinter\Application\Rule\Rules\NamedRule;
68
use ArtARTs36\MergeRequestLinter\Domain\Note\LintNote;
9+
use ArtARTs36\MergeRequestLinter\Domain\Note\Note;
710
use ArtARTs36\MergeRequestLinter\Domain\Request\MergeRequest;
811
use ArtARTs36\MergeRequestLinter\Domain\Rule\RuleDefinition;
912
use ArtARTs36\MergeRequestLinter\Shared\Attributes\Description;
1013
use ArtARTs36\MergeRequestLinter\Shared\Attributes\Example;
1114
use ArtARTs36\MergeRequestLinter\Shared\Attributes\Generic;
1215
use ArtARTs36\MergeRequestLinter\Shared\DataStructure\Arrayee;
16+
use ArtARTs36\Str\Str;
1317

1418
/**
1519
* The title must match conventional commit pattern https://www.conventionalcommits.org/en/v1.0.0.
@@ -19,7 +23,7 @@ final class TitleConventionalRule extends NamedRule
1923
{
2024
public const NAME = '@mr-linter/title_conventional';
2125

22-
private const REGEX = '/^([a-z]+){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)/mis';
26+
private const REGEX = '/^(?<type>([a-z]+)){1}(?<module>\([\w\-\.]+\))?(!)?: (?<description>([\w ])+([\s\S]*))/mis';
2327

2428
private const DEFAULT_TYPES = [
2529
'build',
@@ -41,6 +45,7 @@ final class TitleConventionalRule extends NamedRule
4145
public function __construct(
4246
#[Generic(Generic::OF_STRING)]
4347
private readonly Arrayee $types,
48+
private readonly ?TitleConventionalTask $task,
4449
) {
4550
}
4651

@@ -62,10 +67,12 @@ public static function make(
6267
#[Example('style')]
6368
#[Example('test')]
6469
?Arrayee $types = null,
70+
#[Description('Check if title contains task number')]
71+
?TitleConventionalTask $task = null,
6572
): self {
6673
$types ??= new Arrayee(self::DEFAULT_TYPES);
6774

68-
return new self($types);
75+
return new self($types, $task);
6976
}
7077

7178
public function lint(MergeRequest $request): array
@@ -74,17 +81,24 @@ public function lint(MergeRequest $request): array
7481

7582
preg_match(self::REGEX, $request->title, $matches);
7683

77-
if (! array_key_exists(1, $matches) || ! is_string($matches[1])) {
84+
if (! array_key_exists('type', $matches) || ! is_string($matches['type'])) {
7885
return [new LintNote('The title must matches with conventional commit')];
7986
}
8087

81-
$type = $matches[1];
88+
$type = $matches['type'];
89+
$description = Str::make($matches['description']);
90+
91+
$notes = [];
92+
93+
if (($taskNotes = $this->checkTask($description))) {
94+
$notes = $taskNotes;
95+
}
8296

8397
if (! $this->types->contains($type)) {
84-
return [new LintNote(sprintf('Title conventional: type "%s" is unknown', $type))];
98+
$notes[] = new LintNote(sprintf('Title conventional: type "%s" is unknown', $type));
8599
}
86100

87-
return [];
101+
return $notes;
88102
}
89103

90104
/**
@@ -97,4 +111,39 @@ public function getDefinition(): RuleDefinition
97111
$this->types->implode(', '),
98112
));
99113
}
114+
115+
/**
116+
* @return array<Note>
117+
*/
118+
private function checkTask(Str $description): array
119+
{
120+
if ($this->task === null) {
121+
return [];
122+
}
123+
124+
$projectCode = ProjectCode::findInStart($description);
125+
126+
if ($projectCode === null) {
127+
if (! $this->task->projectCodes->isEmpty()) {
128+
return [new LintNote(
129+
sprintf(
130+
'Description of title must starts with task number of projects ["%s"]',
131+
$this->task->projectCodes->implode(', '),
132+
)
133+
),
134+
];
135+
}
136+
137+
return [new LintNote('Description of title must starts with task number')];
138+
}
139+
140+
if (! $this->task->projectCodes->isEmpty() && ! $this->task->projectCodes->contains($projectCode->__toString())) {
141+
return [new LintNote(sprintf(
142+
'The title contains unknown project code "%s"',
143+
$projectCode,
144+
))];
145+
}
146+
147+
return [];
148+
}
100149
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace ArtARTs36\MergeRequestLinter\Application\Rule\Rules\TitleConventionalRule;
4+
5+
use ArtARTs36\MergeRequestLinter\Shared\Attributes\Description;
6+
use ArtARTs36\MergeRequestLinter\Shared\Attributes\Generic;
7+
use ArtARTs36\MergeRequestLinter\Shared\DataStructure\Arrayee;
8+
9+
/**
10+
* @codeCoverageIgnore
11+
*/
12+
readonly class TitleConventionalTask
13+
{
14+
/**
15+
* @param Arrayee<int, string> $projectCodes
16+
*/
17+
public function __construct(
18+
#[Description('Project codes. Empty list allowed for any projects')]
19+
#[Generic(Generic::OF_STRING)]
20+
public Arrayee $projectCodes = new Arrayee([]),
21+
) {
22+
}
23+
}

src/Infrastructure/Contracts/Rule/RuleResolver.php

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\Rule;
44

55
use ArtARTs36\MergeRequestLinter\Domain\Rule\Rule;
6+
use ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Exceptions\CreatingRuleException;
67
use ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Exceptions\RuleNotFound;
78

89
/**
@@ -14,6 +15,7 @@ interface RuleResolver
1415
* Resolve Rule.
1516
* @param array<mixed> $params
1617
* @throws RuleNotFound
18+
* @throws CreatingRuleException
1719
*/
1820
public function resolve(string $ruleName, array $params): Rule;
1921
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Exceptions;
4+
5+
use ArtARTs36\MergeRequestLinter\Shared\Exceptions\MergeRequestLinterException;
6+
7+
class CreatingRuleException extends MergeRequestLinterException
8+
{
9+
}

src/Infrastructure/Rule/Resolver.php

+12-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use ArtARTs36\MergeRequestLinter\Domain\Rule\Rule;
88
use ArtARTs36\MergeRequestLinter\Infrastructure\Configuration\Exceptions\ConfigInvalidException;
99
use ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\Rule\RuleResolver;
10+
use ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Exceptions\CreatingRuleException;
1011
use ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Exceptions\RuleNotFound;
1112
use ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Factories\ConditionRuleFactory;
1213
use ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Factories\RuleFactory;
@@ -44,6 +45,7 @@ public function resolve(string $ruleName, array $params): Rule
4445
/**
4546
* @param class-string<Rule> $ruleClass
4647
* @param array<int, array<string, mixed>> $params
48+
* @throws CreatingRuleException
4749
*/
4850
private function resolveRuleOnManyConfigurations(string $ruleName, string $ruleClass, array $params): Rule
4951
{
@@ -59,10 +61,19 @@ private function resolveRuleOnManyConfigurations(string $ruleName, string $ruleC
5961
/**
6062
* @param class-string<Rule> $ruleClass
6163
* @param array<string, mixed> $params
64+
* @throws CreatingRuleException
6265
*/
6366
private function resolveRule(string $ruleName, string $ruleClass, array $params): Rule
6467
{
65-
$rule = $this->factory->create($ruleClass, $params);
68+
try {
69+
$rule = $this->factory->create($ruleClass, $params);
70+
} catch (\Throwable $e) {
71+
throw new CreatingRuleException(sprintf(
72+
'Failed to create Rule with name "%s": %s',
73+
$ruleName,
74+
$e->getMessage(),
75+
));
76+
}
6677

6778
if (isset($params['critical']) && $params['critical'] === false) {
6879
$rule = new NonCriticalRule($rule);

src/Version.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99
final class Version
1010
{
11-
public const VERSION = '0.17.0';
11+
public const VERSION = '0.17.1';
1212

1313
private function __construct()
1414
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace ArtARTs36\MergeRequestLinter\Tests\Unit\Application\Rule\Regex;
4+
5+
use ArtARTs36\MergeRequestLinter\Application\Rule\Regex\ProjectCode;
6+
use ArtARTs36\MergeRequestLinter\Tests\TestCase;
7+
use ArtARTs36\Str\Str;
8+
9+
final class ProjectCodeTest extends TestCase
10+
{
11+
public static function providerForFindInStart(): array
12+
{
13+
return [
14+
[
15+
'string' => 'title abc 123',
16+
'expectedProjectCode' => null,
17+
],
18+
[
19+
'string' => 'ABC- title abc 123',
20+
'expectedProjectCode' => null,
21+
],
22+
[
23+
'string' => 'ABC-123 title abc 123',
24+
'expectedProjectCode' => 'ABC',
25+
],
26+
];
27+
}
28+
29+
/**
30+
* @covers \ArtARTs36\MergeRequestLinter\Application\Rule\Regex\ProjectCode::findInStart
31+
*
32+
* @dataProvider providerForFindInStart
33+
*/
34+
public function testFindInStart(string $string, ?string $expectedProjectCode): void
35+
{
36+
self::assertEquals(
37+
$expectedProjectCode,
38+
ProjectCode::findInStart(Str::make($string)),
39+
);
40+
}
41+
}

0 commit comments

Comments
 (0)