Skip to content

Fix: Check for meta type even when using a function reference (fixes #83) #84

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/rules/require-meta-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
71 changes: 66 additions & 5 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand All @@ -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;
Expand Down Expand Up @@ -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);
},

/**
Expand Down
40 changes: 40 additions & 0 deletions tests/lib/rules/require-meta-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -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 = {
Expand Down