diff --git a/docs/rules/require-meta-schema.md b/docs/rules/require-meta-schema.md index 41e5fd84..0783317f 100644 --- a/docs/rules/require-meta-schema.md +++ b/docs/rules/require-meta-schema.md @@ -20,6 +20,13 @@ module.exports = { meta: { schema: null }, create (context) {/* ... */}, }; + +module.exports = { + meta: { schema: [] }, + create (context) { + const options = context.options; /* using options when schema is empty */ + }, +}; ``` Examples of **correct** code for this rule: diff --git a/lib/rules/require-meta-schema.js b/lib/rules/require-meta-schema.js index 143563c8..0b54909c 100644 --- a/lib/rules/require-meta-schema.js +++ b/lib/rules/require-meta-schema.js @@ -28,6 +28,7 @@ module.exports = { }, ], messages: { + foundOptionsUsage: 'Found usage of rule options but no options are defined in `meta.schema`.', missing: '`meta.schema` is required (use [] if rule has no schema).', wrongType: '`meta.schema` should be an array or object (use [] if rule has no schema).', }, @@ -37,15 +38,20 @@ module.exports = { const sourceCode = context.getSourceCode(); const { scopeManager } = sourceCode; const info = utils.getRuleInfo(sourceCode); + if (info === null || info.meta === null) { + return {}; + } + + let contextIdentifiers; + let hasEmptySchema = false; + let schemaNode; return { - Program () { - if (info === null || info.meta === null) { - return; - } + Program (ast) { + contextIdentifiers = utils.getContextIdentifiers(context, ast); const metaNode = info.meta; - const schemaNode = + schemaNode = metaNode && metaNode.properties && metaNode.properties.find(p => p.type === 'Property' && utils.getKeyName(p) === 'schema'); @@ -83,10 +89,31 @@ module.exports = { value = variable.defs[0].node.init; } + if ( + (value.type === 'ArrayExpression' && value.elements.length === 0) || + (value.type === 'ObjectExpression' && value.properties.length === 0) + ) { + // Schema is explicitly defined as having no options. + hasEmptySchema = true; + } + if (!['ArrayExpression', 'ObjectExpression'].includes(value.type)) { context.report({ node: value, messageId: 'wrongType' }); } }, + + MemberExpression (node) { + // Check if `context.options` was used when no options were defined in `meta.schema`. + if ( + hasEmptySchema && + node.object.type === 'Identifier' && + contextIdentifiers.has(node.object) && + node.property.type === 'Identifier' && + node.property.name === 'options' + ) { + context.report({ node: schemaNode, messageId: 'foundOptionsUsage' }); + } + }, }; }, }; diff --git a/tests/lib/rules/require-meta-schema.js b/tests/lib/rules/require-meta-schema.js index 25621162..0a32c60f 100644 --- a/tests/lib/rules/require-meta-schema.js +++ b/tests/lib/rules/require-meta-schema.js @@ -32,6 +32,27 @@ ruleTester.run('require-meta-schema', rule, { create(context) {} }; `, + // Schema with options and using `context.options`. + ` + module.exports = { + meta: { schema: { "enum": ["always", "never"] } }, + create(context) { const options = context.options; } + }; + `, + // Empty schema, using arbitrary property of `context`. + ` + module.exports = { + meta: { schema: [] }, + create(context) { const foo = context.foo; } + }; + `, + // Empty schema, using arbitrary `options` property. + ` + module.exports = { + meta: { schema: [] }, + create(context) { const options = foo.options; } + }; + `, ` const schema = []; module.exports = { @@ -119,5 +140,27 @@ schema: [] }, output: null, errors: [{ messageId: 'wrongType', type: 'Literal' }], }, + { + // Empty schema (array), but using rule options. + code: ` + module.exports = { + meta: { schema: [] }, + create(context) { const options = context.options; } + }; + `, + output: null, + errors: [{ messageId: 'foundOptionsUsage', type: 'Property' }], + }, + { + // Empty schema (object), but using rule options. + code: ` + module.exports = { + meta: { schema: {} }, + create(context) { const options = context.options; } + }; + `, + output: null, + errors: [{ messageId: 'foundOptionsUsage', type: 'Property' }], + }, ], });