/** * @fileoverview Internal rule to prevent missing or invalid meta property in core rules. * @author Vitor Balocco */ "use strict"; //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** * Gets the property of the Object node passed in that has the name specified. * @param {string} property Name of the property to return. * @param {ASTNode} node The ObjectExpression node. * @returns {ASTNode} The Property node or null if not found. */ function getPropertyFromObject(property, node) { const properties = node.properties; if (!Array.isArray(properties)) { return null; } for (let i = 0; i < properties.length; i++) { if (properties[i].key.name === property) { return properties[i]; } } return null; } /** * Extracts the `meta` property from the ObjectExpression that all rules export. * @param {ASTNode} exportsNode ObjectExpression node that the rule exports. * @returns {ASTNode} The `meta` Property node or null if not found. */ function getMetaPropertyFromExportsNode(exportsNode) { return getPropertyFromObject("meta", exportsNode); } /** * Whether this `meta` ObjectExpression has a `docs` property defined or not. * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. * @returns {boolean} `true` if a `docs` property exists. */ function hasMetaDocs(metaPropertyNode) { return Boolean(getPropertyFromObject("docs", metaPropertyNode.value)); } /** * Whether this `meta` ObjectExpression has a `docs.recommended` property defined or not. * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. * @returns {boolean} `true` if a `docs.recommended` property exists. */ function hasMetaDocsRecommended(metaPropertyNode) { const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value); return metaDocs && getPropertyFromObject("recommended", metaDocs.value); } /** * Checks the validity of the meta definition of this rule and reports any errors found. * @param {RuleContext} context The ESLint rule context. * @param {ASTNode} exportsNode ObjectExpression node that the rule exports. * @returns {void} */ function checkMetaValidity(context, exportsNode) { const metaProperty = getMetaPropertyFromExportsNode(exportsNode); if (!metaProperty) { context.report({ node: exportsNode, messageId: "missingMeta" }); return; } if (!hasMetaDocs(metaProperty)) { context.report({ node: metaProperty, messageId: "missingMetaDocs" }); return; } if (!hasMetaDocsRecommended(metaProperty)) { context.report({ node: metaProperty, messageId: "missingMetaDocsRecommended" }); } } //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ module.exports = { meta: { docs: { description: "Enforce correct use of `meta` property in core rules", recommended: false }, type: "problem", schema: [], messages: { missingMeta: "Rule is missing a meta property.", missingMetaDocs: "Rule is missing a meta.docs property.", missingMetaDocsRecommended: "Rule is missing a meta.docs.recommended property.", noExport: "Rule does not export anything. Make sure rule exports an object according to new rule format." } }, create(context) { let exportsNode; return { AssignmentExpression(node) { if (node.left && node.right && node.left.type === "MemberExpression" && node.left.object.name === "module" && node.left.property.name === "exports") { exportsNode = node.right; } }, "Program:exit"(node) { if (!exportsNode) { context.report({ node, messageId: "noExport" }); } else { checkMetaValidity(context, exportsNode); } } }; } };