Skip to content

Commit 69c6e80

Browse files
committed
New: Add new rule prefer-message-ids
1 parent fcbb65c commit 69c6e80

24 files changed

+269
-56
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Name | ✔️ | 🛠 | 💡 | Description
5757
[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`
5858
[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
5959
[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()`
60+
[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
6061
[prefer-object-rule](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-object-rule.md) | | 🛠 | | disallow rule exports where the export is a function
6162
[prefer-output-null](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-output-null.md) | | 🛠 | | disallow invalid RuleTester test cases where the `output` matches the `code`
6263
[prefer-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-placeholders.md) | | | | require using placeholders for dynamic report messages

docs/rules/prefer-message-ids.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Require using `messageId` instead of `message` to report rule violations (prefer-message-ids)
2+
3+
When reporting a rule violation, it's preferred to provide the violation message with the `messageId` property instead of the `message` property. Message IDs provide the following benefits:
4+
5+
* Rule violation messages can be stored in a central `meta.messages` object for convenient management
6+
* Rule violation messages do not need to be repeated in both the rule file and rule test file
7+
8+
## Rule Details
9+
10+
This rule catches usages of the `message` property when reporting a rule violation.
11+
12+
Examples of **incorrect** code for this rule:
13+
14+
```js
15+
/* eslint eslint-plugin/prefer-message-ids: error */
16+
17+
module.exports = {
18+
create (context) {
19+
return {
20+
CallExpression (node) {
21+
context.report({
22+
node,
23+
message: 'Some error message.',
24+
});
25+
},
26+
};
27+
},
28+
};
29+
```
30+
31+
Examples of **correct** code for this rule:
32+
33+
```js
34+
/* eslint eslint-plugin/prefer-message-ids: error */
35+
36+
module.exports = {
37+
meta: {
38+
messages: {
39+
someMessageId: 'Some error message',
40+
},
41+
},
42+
create (context) {
43+
return {
44+
CallExpression (node) {
45+
context.report({
46+
node,
47+
messageId: 'someMessageId',
48+
});
49+
},
50+
};
51+
},
52+
};
53+
```
54+
55+
## Further Reading
56+
57+
* [messageIds API](https://eslint.org/docs/developer-guide/working-with-rules#messageids)

lib/rules/consistent-output.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ module.exports = {
2626
enum: ['always', 'consistent'],
2727
},
2828
],
29+
messages: {
30+
missingOutput: 'This test case should have an output assertion.',
31+
},
2932
},
3033

3134
create (context) {
@@ -48,7 +51,7 @@ module.exports = {
4851
casesWithoutOutput.forEach(testCase => {
4952
context.report({
5053
node: testCase,
51-
message: 'This test case should have an output assertion.',
54+
messageId: 'missingOutput',
5255
});
5356
});
5457
}

lib/rules/meta-property-ordering.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ module.exports = {
2323
type: 'array',
2424
elements: { type: 'string' },
2525
}],
26+
messages: {
27+
inconsistentOrder: 'The meta properties should be placed in a consistent order: [{{order}}].',
28+
},
2629
},
2730

2831
create (context) {
2932
const sourceCode = context.getSourceCode();
3033
const info = getRuleInfo(sourceCode);
3134

32-
const message = 'The meta properties should be placed in a consistent order: [{{order}}].';
3335
const order = context.options[0] || ['type', 'docs', 'fixable', 'schema', 'messages'];
3436

3537
const orderMap = new Map(order.map((name, i) => [name, i]));
@@ -67,7 +69,7 @@ module.exports = {
6769
for (const violatingProp of violatingProps) {
6870
context.report({
6971
node: violatingProp,
70-
message,
72+
messageId: 'inconsistentOrder',
7173
data: {
7274
order: knownProps.map(getKeyName).join(', '),
7375
},

lib/rules/no-deprecated-context-methods.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ module.exports = {
4444
},
4545
fixable: 'code',
4646
schema: [],
47+
messages: {
48+
newFormat: 'Use `{{contextName}}.getSourceCode().{{replacement}}` instead of `{{contextName}}.{{original}}`.',
49+
},
4750
},
4851

4952
create (context) {
@@ -66,7 +69,7 @@ module.exports = {
6669
contextId =>
6770
context.report({
6871
node: contextId.parent,
69-
message: 'Use `{{contextName}}.getSourceCode().{{replacement}}` instead of `{{contextName}}.{{original}}`.',
72+
messageId: 'newFormat',
7073
data: {
7174
contextName: contextId.name,
7275
original: contextId.parent.property.name,

lib/rules/no-deprecated-report-api.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ module.exports = {
2121
},
2222
fixable: 'code', // or "code" or "whitespace"
2323
schema: [],
24+
messages: {
25+
useNewAPI: 'Use the new-style context.report() API.',
26+
},
2427
},
2528

2629
create (context) {
@@ -44,7 +47,7 @@ module.exports = {
4447
) {
4548
context.report({
4649
node: node.callee.property,
47-
message: 'Use the new-style context.report() API.',
50+
messageId: 'useNewAPI',
4851
fix (fixer) {
4952
const openingParen = sourceCode.getTokenBefore(node.arguments[0]);
5053
const closingParen = sourceCode.getLastToken(node);

lib/rules/no-missing-placeholders.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ module.exports = {
2222
},
2323
fixable: null,
2424
schema: [],
25+
messages: {
26+
placeholderDoesNotExist: 'The placeholder {{{{missingKey}}}} does not exist.',
27+
},
2528
},
2629

2730
create (context) {
@@ -66,7 +69,7 @@ module.exports = {
6669
if (!matchingProperty) {
6770
context.report({
6871
node: reportInfo.message,
69-
message: 'The placeholder {{{{missingKey}}}} does not exist.',
72+
messageId: 'placeholderDoesNotExist',
7073
data: { missingKey: match[1] },
7174
});
7275
}

lib/rules/no-unused-placeholders.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ module.exports = {
2222
},
2323
fixable: null,
2424
schema: [],
25+
messages: {
26+
placeholderUnused: 'The placeholder {{{{unusedKey}}}} is unused.',
27+
},
2528
},
2629

2730
create (context) {
@@ -69,7 +72,7 @@ module.exports = {
6972
if (!placeholdersInMessage.has(key)) {
7073
context.report({
7174
node: reportInfo.message,
72-
message: 'The placeholder {{{{unusedKey}}}} is unused.',
75+
messageId: 'placeholderUnused',
7376
data: { unusedKey: key },
7477
});
7578
}

lib/rules/no-useless-token-range.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ module.exports = {
2121
},
2222
fixable: 'code',
2323
schema: [],
24+
messages: {
25+
useReplacement: "Use '{{replacementText}}' instead.",
26+
},
2427
},
2528

2629
create (context) {
@@ -125,7 +128,7 @@ module.exports = {
125128
sourceCode.text.slice(identifier.parent.parent.range[1], fullRangeAccess.range[1]);
126129
context.report({
127130
node: identifier.parent.parent,
128-
message: "Use '{{replacementText}}' instead.",
131+
messageId: 'useReplacement',
129132
data: { replacementText },
130133
fix (fixer) {
131134
return fixer.replaceText(identifier.parent.parent, sourceCode.getText(identifier.parent.parent.arguments[0]));

lib/rules/prefer-message-ids.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict';
2+
3+
const utils = require('../utils');
4+
5+
// ------------------------------------------------------------------------------
6+
// Rule Definition
7+
// ------------------------------------------------------------------------------
8+
9+
module.exports = {
10+
meta: {
11+
type: 'problem',
12+
docs: {
13+
description: 'require using `messageId` instead of `message` to report rule violations',
14+
category: 'Rules',
15+
recommended: false,
16+
},
17+
fixable: null,
18+
schema: [],
19+
messages: {
20+
foundMessage: 'Use `messageId` instead of `message`.',
21+
},
22+
},
23+
24+
create (context) {
25+
let contextIdentifiers;
26+
27+
// ----------------------------------------------------------------------
28+
// Public
29+
// ----------------------------------------------------------------------
30+
31+
return {
32+
Program (ast) {
33+
contextIdentifiers = utils.getContextIdentifiers(context, ast);
34+
},
35+
CallExpression (node) {
36+
if (
37+
node.callee.type === 'MemberExpression' &&
38+
contextIdentifiers.has(node.callee.object) &&
39+
node.callee.property.type === 'Identifier' && node.callee.property.name === 'report'
40+
) {
41+
const reportInfo = utils.getReportInfo(node.arguments, context);
42+
if (!reportInfo || !reportInfo.message) {
43+
return;
44+
}
45+
46+
context.report({
47+
node: reportInfo.message.parent,
48+
messageId: 'foundMessage',
49+
});
50+
}
51+
},
52+
};
53+
},
54+
};

lib/rules/prefer-output-null.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@ module.exports = {
2121
},
2222
fixable: 'code',
2323
schema: [],
24+
messages: {
25+
useOutputNull: 'Use `output: null` to assert that a test case is not autofixed.',
26+
},
2427
},
2528

2629
create (context) {
2730
// ----------------------------------------------------------------------
2831
// Public
2932
// ----------------------------------------------------------------------
3033

31-
const message = 'Use `output: null` to assert that a test case is not autofixed.';
3234
const sourceCode = context.getSourceCode();
3335

3436
return {
@@ -54,7 +56,7 @@ module.exports = {
5456
if (output && sourceCode.getText(output.value) === sourceCode.getText(code.value)) {
5557
context.report({
5658
node: output,
57-
message,
59+
messageId: 'useOutputNull',
5860
fix: fixer => fixer.replaceText(output.value, 'null'),
5961
});
6062
}

lib/rules/prefer-placeholders.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ module.exports = {
2222
},
2323
fixable: null,
2424
schema: [],
25+
messages: {
26+
usePlaceholders: 'Use report message placeholders instead of string concatenation.',
27+
},
2528
},
2629

2730
create (context) {
@@ -80,7 +83,7 @@ module.exports = {
8083
) {
8184
context.report({
8285
node: messageNode,
83-
message: 'Use report message placeholders instead of string concatenation.',
86+
messageId: 'usePlaceholders',
8487
});
8588
}
8689
}

lib/rules/prefer-replace-text.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ module.exports = {
2121
},
2222
fixable: null,
2323
schema: [],
24+
messages: {
25+
useReplaceText: 'Use replaceText instead of replaceTextRange.',
26+
},
2427
},
2528

2629
create (context) {
2730
const sourceCode = context.getSourceCode();
28-
const message = 'Use replaceText instead of replaceTextRange.';
2931
let funcInfo = {
3032
upper: null,
3133
codePath: null,
@@ -74,7 +76,7 @@ module.exports = {
7476
if (isIdenticalNodeRange) {
7577
context.report({
7678
node,
77-
message,
79+
messageId: 'useReplaceText',
7880
});
7981
}
8082
}

lib/rules/report-message-format.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ module.exports = {
2424
schema: [
2525
{ type: 'string' },
2626
],
27+
messages: {
28+
noMatch: "Report message does not match the pattern '{{pattern}}'.",
29+
},
2730
},
2831

2932
create (context) {
@@ -44,7 +47,7 @@ module.exports = {
4447
) {
4548
context.report({
4649
node: message,
47-
message: "Report message does not match the pattern '{{pattern}}'.",
50+
messageId: 'noMatch',
4851
data: { pattern: context.options[0] || '' },
4952
});
5053
}

lib/rules/test-case-property-ordering.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ module.exports = {
2424
type: 'array',
2525
elements: { type: 'string' },
2626
}],
27+
messages: {
28+
inconsistentOrder: 'The properties of a test case should be placed in a consistent order: [{{order}}].',
29+
},
2730
},
2831

2932
create (context) {
3033
// ----------------------------------------------------------------------
3134
// Public
3235
// ----------------------------------------------------------------------
33-
const message = 'The properties of a test case should be placed in a consistent order: [{{order}}].';
3436
const order = context.options[0] || [
3537
'filename',
3638
'code',
@@ -63,7 +65,7 @@ module.exports = {
6365

6466
context.report({
6567
node: properties[i],
66-
message,
68+
messageId: 'inconsistentOrder',
6769
data: { order: orderMsg.join(', ') },
6870
fix (fixer) {
6971
return orderMsg.map((key, index) => {

lib/rules/test-case-shorthand-strings.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ module.exports = {
2121
},
2222
fixable: 'code',
2323
schema: [{ enum: ['as-needed', 'never', 'consistent', 'consistent-as-needed'] }],
24+
messages: {
25+
useShorthand: 'Use {{preferred}} for this test case instead of {{actual}}.',
26+
},
2427
},
2528

2629
create (context) {
@@ -62,7 +65,7 @@ module.exports = {
6265
}[shorthandOption]).forEach(badCaseInfo => {
6366
context.report({
6467
node: badCaseInfo.node,
65-
message: 'Use {{preferred}} for this test case instead of {{actual}}.',
68+
messageId: 'useShorthand',
6669
data: {
6770
preferred: badCaseInfo.shorthand ? 'an object' : 'a string',
6871
actual: badCaseInfo.shorthand ? 'a string' : 'an object',

0 commit comments

Comments
 (0)