diff --git a/lib/rules/require-meta-type.js b/lib/rules/require-meta-type.js index fbad8cad..d9cb5206 100644 --- a/lib/rules/require-meta-type.js +++ b/lib/rules/require-meta-type.js @@ -30,7 +30,7 @@ module.exports = { create (context) { const sourceCode = context.getSourceCode(); - const info = utils.getRuleInfo(sourceCode.ast); + const info = utils.getRuleInfo(sourceCode.ast, sourceCode.scopeManager); // ---------------------------------------------------------------------- // Helpers diff --git a/lib/utils.js b/lib/utils.js index 92a1d13b..bfc29731 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -6,7 +6,58 @@ * @returns {boolean} `true` if the node is a normal function expression */ function isNormalFunctionExpression (node) { - return (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && !node.generator && !node.async; + const functionTypes = [ + 'FunctionExpression', + 'ArrowFunctionExpression', + 'FunctionDeclaration', + ]; + return functionTypes.includes(node.type) && !node.generator && !node.async; +} + +/** +* Determines whether a node is a reference to function expression. +* @param {ASTNode} node The node in question +* @param {ScopeManager} scopeManager The scope manager to use for resolving references +* @returns {boolean} `true` if the node is a reference to a function expression +*/ +function isNormalFunctionExpressionReference (node, scopeManager) { + if (!node || !scopeManager) { + return false; + } + + if (node.type !== 'Identifier') { + return false; + } + + const scope = scopeManager.acquire(node) || scopeManager.globalScope; + if (!scope) { + return false; + } + + const references = scope.references; + const createReference = references.find(reference => { + return reference.identifier === node; + }); + + if (!createReference || !createReference.resolved) { + return false; + } + + const definitions = createReference.resolved.defs; + if (!definitions || !definitions.length) { + return false; + } + + // Assumes it is immediately initialized to a function + let definitionNode = definitions[0].node; + + // If we find something like `const create = () => {}` then send the + // righthand side into the type check. + if (definitionNode.type === 'VariableDeclarator') { + definitionNode = definitionNode.init; + } + + return isNormalFunctionExpression(definitionNode); } /** @@ -33,7 +84,7 @@ module.exports = { is an object, and `false` if module.exports is just the `create` function. If no valid ESLint rule info can be extracted from the file, the return value will be `null`. */ - getRuleInfo (ast) { + getRuleInfo (ast, scopeManager) { const INTERESTING_KEYS = new Set(['create', 'meta']); let exportsVarOverridden = false; let exportsIsFunction = false; @@ -90,9 +141,19 @@ module.exports = { return currentExports; }, {}); - return Object.prototype.hasOwnProperty.call(exportNodes, 'create') && isNormalFunctionExpression(exportNodes.create) - ? Object.assign({ isNewStyle: !exportsIsFunction, meta: null }, exportNodes) - : null; + const createExists = Object.prototype.hasOwnProperty.call(exportNodes, 'create'); + if (!createExists) { + return null; + } + + const createIsFunction = isNormalFunctionExpression(exportNodes.create); + const createIsFunctionReference = isNormalFunctionExpressionReference(exportNodes.create, scopeManager); + + if (!createIsFunction && !createIsFunctionReference) { + return null; + } + + return Object.assign({ isNewStyle: !exportsIsFunction, meta: null }, exportNodes); }, /** diff --git a/tests/lib/rules/require-meta-type.js b/tests/lib/rules/require-meta-type.js index e22bbcd7..41bd6d9f 100644 --- a/tests/lib/rules/require-meta-type.js +++ b/tests/lib/rules/require-meta-type.js @@ -40,6 +40,16 @@ ruleTester.run('require-meta-type', rule, { `module.exports = { create(context) {} }`, + { + code: ` + const create = {}; + module.exports = { + meta: {}, + create, + }; + `, + errors: [{ messageId: 'missing' }], + }, ], invalid: [ @@ -52,6 +62,36 @@ ruleTester.run('require-meta-type', rule, { `, errors: [{ messageId: 'missing' }], }, + { + code: ` + function create(context) {} + module.exports = { + meta: {}, + create, + }; + `, + errors: [{ messageId: 'missing' }], + }, + { + code: ` + const create = function(context) {}; + module.exports = { + meta: {}, + create, + }; + `, + errors: [{ messageId: 'missing' }], + }, + { + code: ` + const create = (context) => {}; + module.exports = { + meta: {}, + create, + }; + `, + errors: [{ messageId: 'missing' }], + }, { code: ` module.exports = {