Skip to content

Commit 6ffddd7

Browse files
authored
Update: Flag a violation when rule options are used but an empty schema is present in require-meta-schema rule (#138)
This new check is similar to how: * `require-meta-fixable` checks that `fixable: true` is specified when an autofixer is found * `require-meta-has-suggestions` checks that `hasSuggestions: true` is specified when suggestions are provided Note: this is *not* a breaking change, since ESLint itself already prevents rules from using options when an empty rule schema is specified, so it's not possible that this new violation could be flagged on existing rules.
1 parent c7f8bee commit 6ffddd7

File tree

3 files changed

+82
-5
lines changed

3 files changed

+82
-5
lines changed

docs/rules/require-meta-schema.md

+7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ module.exports = {
2222
meta: { schema: null },
2323
create (context) {/* ... */},
2424
};
25+
26+
module.exports = {
27+
meta: { schema: [] },
28+
create (context) {
29+
const options = context.options; /* using options when schema is empty */
30+
},
31+
};
2532
```
2633

2734
Examples of **correct** code for this rule:

lib/rules/require-meta-schema.js

+32-5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ module.exports = {
2828
},
2929
],
3030
messages: {
31+
foundOptionsUsage: 'Found usage of rule options but no options are defined in `meta.schema`.',
3132
missing: '`meta.schema` is required (use [] if rule has no schema).',
3233
wrongType: '`meta.schema` should be an array or object (use [] if rule has no schema).',
3334
},
@@ -37,15 +38,20 @@ module.exports = {
3738
const sourceCode = context.getSourceCode();
3839
const { scopeManager } = sourceCode;
3940
const info = utils.getRuleInfo(sourceCode);
41+
if (info === null || info.meta === null) {
42+
return {};
43+
}
44+
45+
let contextIdentifiers;
46+
let hasEmptySchema = false;
47+
let schemaNode;
4048

4149
return {
42-
Program () {
43-
if (info === null || info.meta === null) {
44-
return;
45-
}
50+
Program (ast) {
51+
contextIdentifiers = utils.getContextIdentifiers(context, ast);
4652

4753
const metaNode = info.meta;
48-
const schemaNode =
54+
schemaNode =
4955
metaNode &&
5056
metaNode.properties &&
5157
metaNode.properties.find(p => p.type === 'Property' && utils.getKeyName(p) === 'schema');
@@ -83,10 +89,31 @@ module.exports = {
8389
value = variable.defs[0].node.init;
8490
}
8591

92+
if (
93+
(value.type === 'ArrayExpression' && value.elements.length === 0) ||
94+
(value.type === 'ObjectExpression' && value.properties.length === 0)
95+
) {
96+
// Schema is explicitly defined as having no options.
97+
hasEmptySchema = true;
98+
}
99+
86100
if (!['ArrayExpression', 'ObjectExpression'].includes(value.type)) {
87101
context.report({ node: value, messageId: 'wrongType' });
88102
}
89103
},
104+
105+
MemberExpression (node) {
106+
// Check if `context.options` was used when no options were defined in `meta.schema`.
107+
if (
108+
hasEmptySchema &&
109+
node.object.type === 'Identifier' &&
110+
contextIdentifiers.has(node.object) &&
111+
node.property.type === 'Identifier' &&
112+
node.property.name === 'options'
113+
) {
114+
context.report({ node: schemaNode, messageId: 'foundOptionsUsage' });
115+
}
116+
},
90117
};
91118
},
92119
};

tests/lib/rules/require-meta-schema.js

+43
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@ ruleTester.run('require-meta-schema', rule, {
3232
create(context) {}
3333
};
3434
`,
35+
// Schema with options and using `context.options`.
36+
`
37+
module.exports = {
38+
meta: { schema: { "enum": ["always", "never"] } },
39+
create(context) { const options = context.options; }
40+
};
41+
`,
42+
// Empty schema, using arbitrary property of `context`.
43+
`
44+
module.exports = {
45+
meta: { schema: [] },
46+
create(context) { const foo = context.foo; }
47+
};
48+
`,
49+
// Empty schema, using arbitrary `options` property.
50+
`
51+
module.exports = {
52+
meta: { schema: [] },
53+
create(context) { const options = foo.options; }
54+
};
55+
`,
3556
`
3657
const schema = [];
3758
module.exports = {
@@ -119,5 +140,27 @@ schema: [] },
119140
output: null,
120141
errors: [{ messageId: 'wrongType', type: 'Literal' }],
121142
},
143+
{
144+
// Empty schema (array), but using rule options.
145+
code: `
146+
module.exports = {
147+
meta: { schema: [] },
148+
create(context) { const options = context.options; }
149+
};
150+
`,
151+
output: null,
152+
errors: [{ messageId: 'foundOptionsUsage', type: 'Property' }],
153+
},
154+
{
155+
// Empty schema (object), but using rule options.
156+
code: `
157+
module.exports = {
158+
meta: { schema: {} },
159+
create(context) { const options = context.options; }
160+
};
161+
`,
162+
output: null,
163+
errors: [{ messageId: 'foundOptionsUsage', type: 'Property' }],
164+
},
122165
],
123166
});

0 commit comments

Comments
 (0)