Skip to content

Commit 105f05d

Browse files
Add new rule no-meta-replaced-by (#518)
* feat: add `no-meta-replaced-by` rule * test: add `no-meta-replaced-by` tests * docs: add `no-meta-replaced-by` docs * docs: add `When Not To Use It` * fix: update description and recommendation status for rule * test: add valid test case * docs: clarify description of rule and update examples * fix: cleanup jsdoc * docs: update examples of correct code * docs: remove `When not to use it` Co-authored-by: Bryan Mishkin <[email protected]> * Update docs/rules/no-meta-replaced-by.md --------- Co-authored-by: Bryan Mishkin <[email protected]>
1 parent 735b983 commit 105f05d

File tree

4 files changed

+265
-0
lines changed

4 files changed

+265
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ module.exports = [
8787
| [meta-property-ordering](docs/rules/meta-property-ordering.md) | enforce the order of meta properties | | 🔧 | | |
8888
| [no-deprecated-context-methods](docs/rules/no-deprecated-context-methods.md) | disallow usage of deprecated methods on rule context objects || 🔧 | | |
8989
| [no-deprecated-report-api](docs/rules/no-deprecated-report-api.md) | disallow the version of `context.report()` with multiple arguments || 🔧 | | |
90+
| [no-meta-replaced-by](docs/rules/no-meta-replaced-by.md) | disallow using the `meta.replacedBy` rule property | | | | |
9091
| [no-meta-schema-default](docs/rules/no-meta-schema-default.md) | disallow rules `meta.schema` properties to include defaults | | | | |
9192
| [no-missing-message-ids](docs/rules/no-missing-message-ids.md) | disallow `messageId`s that are missing from `meta.messages` || | | |
9293
| [no-missing-placeholders](docs/rules/no-missing-placeholders.md) | disallow missing placeholders in rule report messages || | | |

docs/rules/no-meta-replaced-by.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Disallow using the `meta.replacedBy` rule property (`eslint-plugin/no-meta-replaced-by`)
2+
3+
<!-- end auto-generated rule header -->
4+
5+
As of ESLint v9.21.0, the rule property `meta.deprecated` can be either a boolean or an object of type `DeprecatedInfo`. The `DeprecatedInfo` type includes an optional `replacedBy` array that replaces the now-deprecated `meta.replacedBy` property.
6+
7+
Examples of correct usage:
8+
9+
- [array-bracket-newline](https://github.com/eslint/eslint/blob/4112fd09531092e9651e9981205bcd603dc56acf/lib/rules/array-bracket-newline.js#L18-L38)
10+
- [typescript-eslint/no-empty-interface](https://github.com/typescript-eslint/typescript-eslint/blob/af94f163a1d6447a84c5571fff5e38e4c700edb9/packages/eslint-plugin/src/rules/no-empty-interface.ts#L19-L30)
11+
12+
## Rule Details
13+
14+
This rule disallows the `meta.replacedBy` property in a rule.
15+
16+
Examples of **incorrect** code for this rule:
17+
18+
```js
19+
/* eslint eslint-plugin/no-meta-replaced-by: error */
20+
21+
module.exports = {
22+
meta: {
23+
deprecated: true,
24+
replacedBy: ['the-new-rule'],
25+
},
26+
create() {},
27+
};
28+
```
29+
30+
Examples of **correct** code for this rule:
31+
32+
```js
33+
/* eslint eslint-plugin/no-meta-replaced-by: error */
34+
35+
module.exports = {
36+
meta: {
37+
deprecated: {
38+
message: 'The new rule adds more functionality',
39+
replacedBy: [
40+
{
41+
rule: {
42+
name: 'the-new-rule',
43+
},
44+
},
45+
],
46+
},
47+
},
48+
create() {},
49+
};
50+
51+
module.exports = {
52+
meta: {
53+
deprecated: true,
54+
},
55+
create() {},
56+
};
57+
```
58+
59+
## Further Reading
60+
61+
- [ESLint docs: `DeprecatedInfo`](https://eslint.org/docs/latest/extend/rule-deprecation#-deprecatedinfo-type)
62+
- [RFC introducing `DeprecatedInfo` type](https://github.com/eslint/rfcs/tree/main/designs/2024-deprecated-rule-metadata)

lib/rules/no-meta-replaced-by.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* @fileoverview Disallows the usage of `meta.replacedBy` property
3+
*/
4+
5+
'use strict';
6+
7+
const utils = require('../utils');
8+
9+
// ------------------------------------------------------------------------------
10+
// Rule Definition
11+
// ------------------------------------------------------------------------------
12+
13+
/** @type {import('eslint').Rule.RuleModule} */
14+
module.exports = {
15+
meta: {
16+
type: 'problem',
17+
docs: {
18+
description: 'disallow using the `meta.replacedBy` rule property',
19+
category: 'Rules',
20+
recommended: false,
21+
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-meta-replaced-by.md',
22+
},
23+
schema: [],
24+
messages: {
25+
useNewFormat:
26+
'Use `meta.deprecated.replacedBy` instead of `meta.replacedBy`',
27+
},
28+
},
29+
create(context) {
30+
const sourceCode = utils.getSourceCode(context);
31+
const ruleInfo = utils.getRuleInfo(sourceCode);
32+
33+
if (!ruleInfo) {
34+
return {};
35+
}
36+
37+
return {
38+
Program() {
39+
const metaNode = ruleInfo.meta;
40+
41+
if (!metaNode) {
42+
return;
43+
}
44+
45+
const replacedByNode = utils
46+
.evaluateObjectProperties(metaNode, sourceCode.scopeManager)
47+
.find(
48+
(p) =>
49+
p.type === 'Property' && utils.getKeyName(p) === 'replacedBy',
50+
);
51+
52+
if (!replacedByNode) {
53+
return;
54+
}
55+
56+
context.report({
57+
node: replacedByNode,
58+
messageId: 'useNewFormat',
59+
});
60+
},
61+
};
62+
},
63+
};
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* @fileoverview Disallows the usage of `meta.replacedBy` property
3+
*/
4+
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const rule = require('../../../lib/rules/no-meta-replaced-by');
12+
const RuleTester = require('../eslint-rule-tester').RuleTester;
13+
14+
// ------------------------------------------------------------------------------
15+
// Tests
16+
// ------------------------------------------------------------------------------
17+
18+
const valid = [
19+
'module.exports = {};',
20+
`
21+
module.exports = {
22+
create(context) {},
23+
};
24+
`,
25+
`
26+
module.exports = {
27+
meta: {},
28+
create(context) {},
29+
};
30+
`,
31+
`
32+
module.exports = {
33+
meta: {
34+
deprecated: true,
35+
},
36+
create(context) {},
37+
};
38+
`,
39+
{
40+
code: `
41+
module.exports = {
42+
meta: {
43+
deprecated: {
44+
replacedBy: [
45+
{
46+
rule: {
47+
name: 'foo',
48+
},
49+
},
50+
],
51+
},
52+
},
53+
create(context) {},
54+
};
55+
`,
56+
errors: 0,
57+
},
58+
];
59+
60+
const invalid = [
61+
{
62+
code: `
63+
module.exports = {
64+
meta: {
65+
replacedBy: ['the-new-rule'],
66+
},
67+
create(context) {},
68+
};
69+
`,
70+
errors: [
71+
{
72+
messageId: 'useNewFormat',
73+
line: 4,
74+
endLine: 4,
75+
},
76+
],
77+
},
78+
{
79+
code: `
80+
const meta = {
81+
replacedBy: null,
82+
};
83+
84+
module.exports = {
85+
meta,
86+
create(context) {},
87+
};
88+
`,
89+
errors: [
90+
{
91+
messageId: 'useNewFormat',
92+
line: 3,
93+
endLine: 3,
94+
},
95+
],
96+
},
97+
{
98+
code: `
99+
const spread = {
100+
replacedBy: null,
101+
};
102+
103+
module.exports = {
104+
meta: {
105+
...spread,
106+
},
107+
create(context) {},
108+
};
109+
`,
110+
errors: [{ messageId: 'useNewFormat' }],
111+
},
112+
];
113+
114+
const testToESM = (test) => {
115+
if (typeof test === 'string') {
116+
return test.replace('module.exports =', 'export default');
117+
}
118+
119+
const code = test.code.replace('module.exports =', 'export default');
120+
121+
return {
122+
...test,
123+
code,
124+
};
125+
};
126+
127+
new RuleTester({
128+
languageOptions: { sourceType: 'commonjs' },
129+
}).run('no-meta-replaced-by', rule, {
130+
valid,
131+
invalid,
132+
});
133+
134+
new RuleTester({
135+
languageOptions: { sourceType: 'module' },
136+
}).run('no-meta-replaced-by', rule, {
137+
valid: valid.map(testToESM),
138+
invalid: invalid.map(testToESM),
139+
});

0 commit comments

Comments
 (0)