|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +/** |
| 4 | +* Determines whether a node is a 'normal' (i.e. non-async, non-generator) function expression. |
| 5 | +* @param {ASTNode} node The node in question |
| 6 | +* @returns {boolean} `true` if the node is a normal function expression |
| 7 | +*/ |
| 8 | +function isNormalFunctionExpression (node) { |
| 9 | + return (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && !node.generator && !node.async; |
| 10 | +} |
| 11 | + |
| 12 | +module.exports = { |
| 13 | + |
| 14 | + /** |
| 15 | + * Performs static analysis on an AST to try to determine the final value of `module.exports`. |
| 16 | + * @param {ASTNode} ast The `Program` AST node |
| 17 | + * @returns {Object} An object with keys `meta`, `create`, and `isNewStyle`. `meta` and `create` correspond to the AST nodes |
| 18 | + for the final values of `module.exports.meta` and `module.exports.create`. `isNewStyle` will be `true` if `module.exports` |
| 19 | + is an object, and `false` if module.exports is just the `create` function. If no valid ESLint rule info can be extracted |
| 20 | + from the file, the return value will be `null`. |
| 21 | + */ |
| 22 | + getRuleInfo (ast) { |
| 23 | + const INTERESTING_KEYS = ['create', 'meta']; |
| 24 | + let exportsVarOverridden = false; |
| 25 | + let exportsIsFunction = false; |
| 26 | + |
| 27 | + const exportNodes = ast.body |
| 28 | + .filter(statement => statement.type === 'ExpressionStatement') |
| 29 | + .map(statement => statement.expression) |
| 30 | + .filter(expression => expression.type === 'AssignmentExpression') |
| 31 | + .filter(expression => expression.left.type === 'MemberExpression') |
| 32 | + .reduce((currentExports, node) => { |
| 33 | + if ( |
| 34 | + node.left.object.type === 'Identifier' && node.left.object.name === 'module' && |
| 35 | + node.left.property.type === 'Identifier' && node.left.property.name === 'exports' |
| 36 | + ) { |
| 37 | + exportsVarOverridden = true; |
| 38 | + |
| 39 | + if (isNormalFunctionExpression(node.right)) { |
| 40 | + // Check `module.exports = function () {}` |
| 41 | + |
| 42 | + exportsIsFunction = true; |
| 43 | + return { create: node.right, meta: null }; |
| 44 | + } else if (node.right.type === 'ObjectExpression') { |
| 45 | + // Check `module.exports = { create: function () {}, meta: {} }` |
| 46 | + |
| 47 | + exportsIsFunction = false; |
| 48 | + return node.right.properties.reduce((parsedProps, prop) => { |
| 49 | + const keyValue = prop.key.type === 'Literal' |
| 50 | + ? prop.key.value |
| 51 | + : prop.key.type === 'Identifier' |
| 52 | + ? prop.key.name |
| 53 | + : null; |
| 54 | + |
| 55 | + if (INTERESTING_KEYS.indexOf(keyValue) !== -1) { |
| 56 | + parsedProps[keyValue] = prop.value; |
| 57 | + } |
| 58 | + return parsedProps; |
| 59 | + }, {}); |
| 60 | + } |
| 61 | + return {}; |
| 62 | + } else if ( |
| 63 | + !exportsIsFunction && |
| 64 | + node.left.object.type === 'MemberExpression' && |
| 65 | + node.left.object.object.type === 'Identifier' && node.left.object.object.name === 'module' && |
| 66 | + node.left.object.property.type === 'Identifier' && node.left.object.property.name === 'exports' && |
| 67 | + node.left.property.type === 'Identifier' && INTERESTING_KEYS.indexOf(node.left.property.name) !== -1 |
| 68 | + ) { |
| 69 | + // Check `module.exports.create = () => {}` |
| 70 | + |
| 71 | + currentExports[node.left.property.name] = node.right; |
| 72 | + } else if ( |
| 73 | + !exportsVarOverridden && |
| 74 | + node.left.object.type === 'Identifier' && node.left.object.name === 'exports' && |
| 75 | + node.left.property.type === 'Identifier' && INTERESTING_KEYS.indexOf(node.left.property.name) !== -1 |
| 76 | + ) { |
| 77 | + // Check `exports.create = () => {}` |
| 78 | + |
| 79 | + currentExports[node.left.property.name] = node.right; |
| 80 | + } |
| 81 | + return currentExports; |
| 82 | + }, {}); |
| 83 | + |
| 84 | + return Object.prototype.hasOwnProperty.call(exportNodes, 'create') && isNormalFunctionExpression(exportNodes.create) |
| 85 | + ? Object.assign({ isNewStyle: !exportsIsFunction, meta: null }, exportNodes) |
| 86 | + : null; |
| 87 | + }, |
| 88 | +}; |
0 commit comments