Skip to content

Commit ff6f9e5

Browse files
youngji0827YJ Lee
andauthored
Add no-jira-ticket rule (#13)
* add a rule to error when a TODO does not have a JIRA ticket * pr comments * remove log and update license * update to support multi TODO files --------- Co-authored-by: YJ Lee <youngji.lee@skyscanner.net>
1 parent 96ee8f2 commit ff6f9e5

File tree

4 files changed

+188
-0
lines changed

4 files changed

+188
-0
lines changed

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
const noAxios = require('./src/rules/no-axios/no-axios');
1818
const forbidComponentProps = require('./src/rules/forbid-component-props/forbid-component-props');
19+
const noJiraTodo = require('./src/rules/no-jira-todo/no-jira-todo');
1920

2021
module.exports = {
2122
rules: {
2223
'no-axios': noAxios,
2324
'forbid-component-props': forbidComponentProps,
25+
'no-jira-todo': noJiraTodo,
2426
},
2527
};

src/rules/no-jira-todo/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
## Asserting all TODO comments have a linked JIRA ticket
2+
3+
This rule when enabled would require every `TODO` style comnent to be linked to an associated JIRA ticket.
4+
5+
The format of the comment is required to match:
6+
7+
- `TODO:`/`FIXME:`/`@TODO:`/`@FIXME:`
8+
- followed by the Jira url or the ticket reference in square brackets, eg:
9+
- `https://skyscanner.atlassian.net/browse/JIRA-0000`
10+
- `[JIRA-0000]`
11+
- then an optional description about the changes needed by the linked ticket
12+
13+
Examples of valid comments:
14+
15+
```tsx
16+
// TODO: [WALL-1234] description of the change needed
17+
// FIXME: [CASTLE-5678]
18+
// FIXME: [WOM-2468] comment
19+
// @TODO: [WALL-1234] description of the change needed
20+
// @FIXME: [WALL-1234] description of the change needed
21+
// TODO: https://skyscanner.atlassian.net/browse/SHIBA-1234 description of the change needed
22+
// @FIXME: https://skyscanner.atlassian.net/browse/WOODPECKER-1010 description of the change needed
23+
// TODO: [JIRA-XXXX] fixed
24+
```
25+
26+
Examples of invalid comments:
27+
28+
```tsx
29+
// TODO: WALL-1234 description of the change needed
30+
// TODO: description of the change needed
31+
// FIXME description of the change needed WALL-1234
32+
// @FIXME description of the change needed WALL-1234
33+
// @TODO: https://skyscanner.atlassian.net/browse/not-a-ticket
34+
```
35+
36+
If an invalid `TODO` comment is recognised, there is a auto fix available to update the comment to the format. This should be updated with the ticket and description. This is provided in the format:
37+
38+
```tsx
39+
// TODO: [JIRA-XXX]
40+
```
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Copyright 2023-present Skyscanner Limited.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// ------------------------------------------------------------------------------
18+
// Rule Definition
19+
// ------------------------------------------------------------------------------
20+
21+
const messages = {
22+
'todo-error':
23+
'JIRA ticket numbers must be added to all TODO/FIXME comments (e.g. TODO: DINGO-123)',
24+
};
25+
26+
const verifyCommentPrefix = (comment) =>
27+
['TODO', 'FIXME', '@TODO', '@FIXME'].find((prefix) =>
28+
comment.startsWith(prefix),
29+
);
30+
31+
module.exports = {
32+
meta: {
33+
docs: {
34+
description: 'All TODO comments must have a linked JIRA ticket',
35+
category: 'Best Practices',
36+
recommended: 'error',
37+
url: 'https://github.com/Skyscanner/eslint-plugin-rules#no-jira-todo',
38+
},
39+
messages,
40+
type: 'suggestion',
41+
schema: [],
42+
fixable: 'code',
43+
},
44+
create(context) {
45+
return {
46+
Program() {
47+
const prefix = new RegExp('@?(TODO|FIXME)');
48+
const ticket = new RegExp('[A-Z]{2,255}-(\\d|X){2,8}');
49+
const jiraUrl = new RegExp(
50+
`https://([a-zA-Z0-9.\/-]\+)/${ticket.source}`,
51+
);
52+
const regex = new RegExp(
53+
`${prefix.source}:\\s(${jiraUrl.source}|\\[\?${ticket.source}\\]\?)(\\s.*)?`,
54+
);
55+
56+
for (const comment of context.getSourceCode().getAllComments()) {
57+
const value = comment.value.trimStart();
58+
59+
if (verifyCommentPrefix(value) && !regex.test(value)) {
60+
context.report({
61+
node: comment,
62+
messageId: 'todo-error',
63+
fix: (fixer) =>
64+
fixer.replaceText(comment, '// TODO: [JIRA-XXXX]'),
65+
});
66+
}
67+
}
68+
},
69+
};
70+
},
71+
};
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Copyright 2023-present Skyscanner Limited.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
const { RuleTester } = require('eslint');
17+
18+
const rule = require('./no-jira-todo');
19+
20+
const parserOptions = {
21+
ecmaVersion: 2018,
22+
sourceType: 'module',
23+
ecmaFeatures: {
24+
jsx: true,
25+
},
26+
};
27+
28+
const ruleTester = new RuleTester({ parserOptions });
29+
30+
ruleTester.run('no-jira-todo', rule, {
31+
valid: [
32+
'const variable = "Non comment"',
33+
'// TODO: [WALL-1234] description',
34+
'// TODO: DINGO-123 description',
35+
'// TODO: [JIRA-XXXX] fixed',
36+
'// FIXME: [CASTLE-5678]',
37+
'// FIXME: [WOM-2468] comment',
38+
'// @TODO: [WALL-1234] description',
39+
'// @FIXME: [WALL-1234] description',
40+
'// TODO: https://skyscanner.atlassian.net/browse/SHIBA-1234 description',
41+
'// TODO: https://atlassian-upgrade.net/browse/WALL-1234',
42+
'// @FIXME: https://skyscanner.atlassian.net/browse/WOODPECKER-1010 description',
43+
'const regex = new RegExp("expression", "i"); // TODO: [JIRA-4321] comment',
44+
`// TODO: ST-123 first of multiple TODOs
45+
const { locale } = context[requestContextParameters.CULTURE];
46+
// TODO: DINGO-123 another TODO`,
47+
],
48+
invalid: [
49+
{
50+
code: '// TODO: please fail',
51+
errors: [{ messageId: 'todo-error' }],
52+
output: '// TODO: [JIRA-XXXX]',
53+
},
54+
{
55+
code: '// TODO: this should also fail WALL-1234',
56+
errors: [{ messageId: 'todo-error' }],
57+
output: '// TODO: [JIRA-XXXX]',
58+
},
59+
{
60+
code: '// FIXME another fail WALL-1234',
61+
errors: [{ messageId: 'todo-error' }],
62+
output: '// TODO: [JIRA-XXXX]',
63+
},
64+
{
65+
code: '// @FIXME and this too [WALL-1234]',
66+
errors: [{ messageId: 'todo-error' }],
67+
output: '// TODO: [JIRA-XXXX]',
68+
},
69+
{
70+
code: '// @TODO: https://skyscanner.atlassian.net/browse/not-a-ticket',
71+
errors: [{ messageId: 'todo-error' }],
72+
output: '// TODO: [JIRA-XXXX]',
73+
},
74+
],
75+
});

0 commit comments

Comments
 (0)