Skip to content

Commit 806decb

Browse files
committed
feat: add new rules no-missing-message-ids and no-unused-message-ids
1 parent 34bcb74 commit 806decb

10 files changed

+1199
-1
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ Name | ✔️ | 🛠 | 💡 | Description
6767
[no-deprecated-context-methods](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-deprecated-context-methods.md) | ✔️ | 🛠 | | disallow usage of deprecated methods on rule context objects
6868
[no-deprecated-report-api](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-deprecated-report-api.md) | ✔️ | 🛠 | | disallow the version of `context.report()` with multiple arguments
6969
[no-identical-tests](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-identical-tests.md) | ✔️ | 🛠 | | disallow identical tests
70+
[no-missing-message-ids](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-message-ids.md) | | | | disallow `messageId`s that are missing from `meta.messages`
7071
[no-missing-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-placeholders.md) | ✔️ | | | disallow missing placeholders in rule report messages
7172
[no-only-tests](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-only-tests.md) | ✔️ | | 💡 | disallow the test case property `only`
73+
[no-unused-message-ids](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-unused-message-ids.md) | | | | disallow unused `messageId`s in `meta.messages`
7274
[no-unused-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-unused-placeholders.md) | ✔️ | | | disallow unused placeholders in rule report messages
7375
[no-useless-token-range](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-useless-token-range.md) | ✔️ | 🛠 | | disallow unnecessary calls to `sourceCode.getFirstToken()` and `sourceCode.getLastToken()`
7476
[prefer-message-ids](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-message-ids.md) | | | | require using `messageId` instead of `message` to report rule violations

docs/rules/no-missing-message-ids.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Disallow `messageId`s that are missing from `meta.messages` (no-missing-message-ids)
2+
3+
When using `meta.messages` and `messageId` to report rule violations, it's possible to mistakenly use a `messageId` that doesn't exist in `meta.messages`.
4+
5+
## Rule Details
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```js
10+
/* eslint eslint-plugin/no-missing-message-ids: error */
11+
12+
module.exports = {
13+
meta: {
14+
messages: {
15+
foo: 'hello world',
16+
},
17+
},
18+
create(context) {
19+
return {
20+
CallExpression(node) {
21+
context.report({
22+
node,
23+
messageId: 'abc',
24+
});
25+
},
26+
};
27+
},
28+
};
29+
```
30+
31+
Examples of **correct** code for this rule:
32+
33+
```js
34+
/* eslint eslint-plugin/no-missing-message-ids: error */
35+
36+
module.exports = {
37+
meta: {
38+
messages: {
39+
foo: 'hello world',
40+
},
41+
},
42+
create(context) {
43+
return {
44+
CallExpression(node) {
45+
context.report({
46+
node,
47+
messageId: 'foo',
48+
});
49+
},
50+
};
51+
},
52+
};
53+
```
54+
55+
## Further Reading
56+
57+
* [messageIds API](https://eslint.org/docs/developer-guide/working-with-rules#messageids)
58+
* [no-unused-message-ids](./no-unused-message-ids.md) rule
59+
* [prefer-message-ids](./prefer-message-ids.md) rule

docs/rules/no-unused-message-ids.md

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Disallow unused `messageId`s in `meta.messages` (no-unused-message-ids)
2+
3+
When using `meta.messages` and `messageId` to report rule violations, it's possible to mistakenly leave a message in `meta.messages` that is never used.
4+
5+
## Rule Details
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```js
10+
/* eslint eslint-plugin/no-unused-message-ids: error */
11+
12+
module.exports = {
13+
meta: {
14+
messages: {
15+
foo: 'hello world',
16+
bar: 'lorem ipsum', // this message is never used
17+
},
18+
},
19+
create(context) {
20+
return {
21+
CallExpression(node) {
22+
context.report({
23+
node,
24+
messageId: 'foo',
25+
});
26+
},
27+
};
28+
},
29+
};
30+
```
31+
32+
Examples of **correct** code for this rule:
33+
34+
```js
35+
/* eslint eslint-plugin/no-unused-message-ids: error */
36+
37+
module.exports = {
38+
meta: {
39+
messages: {
40+
foo: 'hello world',
41+
},
42+
},
43+
create(context) {
44+
return {
45+
CallExpression(node) {
46+
context.report({
47+
node,
48+
messageId: 'foo',
49+
});
50+
},
51+
};
52+
},
53+
};
54+
```
55+
56+
## Further Reading
57+
58+
* [messageIds API](https://eslint.org/docs/developer-guide/working-with-rules#messageids)
59+
* [no-missing-message-ids](./no-missing-message-ids.md) rule
60+
* [prefer-message-ids](./prefer-message-ids.md) rule

docs/rules/prefer-message-ids.md

+2
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,5 @@ module.exports = {
5555
## Further Reading
5656

5757
* [messageIds API](https://eslint.org/docs/developer-guide/working-with-rules#messageids)
58+
* [no-invalid-message-ids](./no-invalid-message-ids.md) rule
59+
* [no-missing-message-ids](./no-missing-message-ids.md) rule

lib/rules/no-missing-message-ids.js

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
'use strict';
2+
3+
const utils = require('../utils');
4+
5+
// ------------------------------------------------------------------------------
6+
// Rule Definition
7+
// ------------------------------------------------------------------------------
8+
9+
/** @type {import('eslint').Rule.RuleModule} */
10+
module.exports = {
11+
meta: {
12+
type: 'problem',
13+
docs: {
14+
description:
15+
'disallow `messageId`s that are missing from `meta.messages`',
16+
category: 'Rules',
17+
recommended: false,
18+
url: 'https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-missing-message-ids.md',
19+
},
20+
fixable: null,
21+
schema: [],
22+
messages: {
23+
missingMessage: '`meta.messages` is missing this `messageId`.',
24+
},
25+
},
26+
27+
create(context) {
28+
const sourceCode = context.getSourceCode();
29+
const { scopeManager } = sourceCode;
30+
const ruleInfo = utils.getRuleInfo(sourceCode);
31+
32+
const messagesNode = utils.getMessagesNode(ruleInfo, scopeManager);
33+
34+
let contextIdentifiers;
35+
36+
// ----------------------------------------------------------------------
37+
// Public
38+
// ----------------------------------------------------------------------
39+
40+
if (!messagesNode || messagesNode.type !== 'ObjectExpression') {
41+
// If we can't find `meta.messages`, disable the rule.
42+
return {};
43+
}
44+
45+
return {
46+
Program(ast) {
47+
contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast);
48+
},
49+
50+
CallExpression(node) {
51+
if (
52+
node.callee.type === 'MemberExpression' &&
53+
contextIdentifiers.has(node.callee.object) &&
54+
node.callee.property.type === 'Identifier' &&
55+
node.callee.property.name === 'report'
56+
) {
57+
const reportInfo = utils.getReportInfo(node.arguments, context);
58+
if (!reportInfo) {
59+
return;
60+
}
61+
62+
const reportMessagesAndDataArray =
63+
utils.collectReportViolationAndSuggestionData(reportInfo);
64+
65+
for (const { messageId } of reportMessagesAndDataArray.filter(
66+
(obj) => obj.messageId
67+
)) {
68+
const values =
69+
messageId.type === 'Literal'
70+
? [messageId]
71+
: utils.findPossibleVariableValues(messageId, scopeManager);
72+
73+
// Look for any possible string values we found for this messageId.
74+
values.forEach((val) => {
75+
if (
76+
val.type === 'Literal' &&
77+
val.value !== null &&
78+
val.value !== '' &&
79+
!utils.getMessageIdNodeById(val.value, ruleInfo, scopeManager)
80+
)
81+
// Couldn't find this messageId in `meta.messages`.
82+
context.report({
83+
node: val,
84+
messageId: 'missingMessage',
85+
});
86+
});
87+
}
88+
}
89+
},
90+
};
91+
},
92+
};

lib/rules/no-unused-message-ids.js

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
'use strict';
2+
3+
const utils = require('../utils');
4+
5+
// ------------------------------------------------------------------------------
6+
// Rule Definition
7+
// ------------------------------------------------------------------------------
8+
9+
/** @type {import('eslint').Rule.RuleModule} */
10+
module.exports = {
11+
meta: {
12+
type: 'problem',
13+
docs: {
14+
description: 'disallow unused `messageId`s in `meta.messages`',
15+
category: 'Rules',
16+
recommended: false,
17+
url: 'https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-unused-message-ids.md',
18+
},
19+
fixable: null,
20+
schema: [],
21+
messages: {
22+
unusedMessage: 'This message is never used.',
23+
},
24+
},
25+
26+
create(context) {
27+
const sourceCode = context.getSourceCode();
28+
const { scopeManager } = sourceCode;
29+
const info = utils.getRuleInfo(sourceCode);
30+
31+
const messageIdsUsed = new Set();
32+
let contextIdentifiers;
33+
let shouldPerformUnusedCheck = true;
34+
35+
const messageIdNodes = utils.getMessageIdNodes(info, scopeManager);
36+
if (!messageIdNodes) {
37+
// If we can't find `meta.messages`, disable the rule.
38+
return {};
39+
}
40+
41+
// ----------------------------------------------------------------------
42+
// Public
43+
// ----------------------------------------------------------------------
44+
45+
return {
46+
Program(ast) {
47+
contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast);
48+
},
49+
50+
'Program:exit'() {
51+
if (shouldPerformUnusedCheck) {
52+
for (const messageIdNode of messageIdNodes.filter(
53+
(node) => !messageIdsUsed.has(node.key.name)
54+
)) {
55+
context.report({
56+
node: messageIdNode,
57+
messageId: 'unusedMessage',
58+
});
59+
}
60+
}
61+
},
62+
63+
CallExpression(node) {
64+
// Check for messageId properties use in known calls to context.report();
65+
if (
66+
node.callee.type === 'MemberExpression' &&
67+
contextIdentifiers.has(node.callee.object) &&
68+
node.callee.property.type === 'Identifier' &&
69+
node.callee.property.name === 'report'
70+
) {
71+
const reportInfo = utils.getReportInfo(node.arguments, context);
72+
if (!reportInfo) {
73+
return;
74+
}
75+
76+
const reportMessagesAndDataArray =
77+
utils.collectReportViolationAndSuggestionData(reportInfo);
78+
79+
for (const { messageId } of reportMessagesAndDataArray.filter(
80+
(obj) => obj.messageId
81+
)) {
82+
const values =
83+
messageId.type === 'Literal'
84+
? [messageId]
85+
: utils.findPossibleVariableValues(messageId, scopeManager);
86+
if (
87+
values.length === 0 ||
88+
values.some((val) => val.type !== 'Literal')
89+
) {
90+
// When a dynamic messageId is used and we can't detect its value, disable the rule to avoid false positives.
91+
shouldPerformUnusedCheck = false;
92+
}
93+
values.forEach((val) => messageIdsUsed.add(val.value));
94+
}
95+
}
96+
},
97+
98+
Property(node) {
99+
// In order to reduce false positives, we will also check for messageId properties anywhere in the file.
100+
// This is helpful especially in the event that helper functions are used for reporting violations.
101+
if (node.key.type === 'Identifier' && node.key.name === 'messageId') {
102+
const values =
103+
node.value.type === 'Literal'
104+
? [node.value]
105+
: utils.findPossibleVariableValues(node.value, scopeManager);
106+
if (
107+
values.length === 0 ||
108+
values.some((val) => val.type !== 'Literal')
109+
) {
110+
// When a dynamic messageId is used and we can't detect its value, disable the rule to avoid false positives.
111+
shouldPerformUnusedCheck = false;
112+
}
113+
values.forEach((val) => messageIdsUsed.add(val.value));
114+
}
115+
},
116+
};
117+
},
118+
};

0 commit comments

Comments
 (0)