Skip to content

Commit

Permalink
extract ESM/CSJ getRuleInfo into separate functions
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish committed Sep 23, 2021
1 parent fcfdec0 commit 7a12707
Showing 1 changed file with 84 additions and 80 deletions.
164 changes: 84 additions & 80 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ function isNormalFunctionExpressionReference (node, scopeManager) {
return isNormalFunctionExpression(definitionNode);
}

const INTERESTING_RULE_KEYS = new Set(['create', 'meta']);

/**
* Determines whether a node is constructing a RuleTester instance
* @param {ASTNode} node The node in question
Expand All @@ -83,8 +85,87 @@ function isRuleTesterConstruction (node) {
);
}

module.exports = {
/**
* Helper for `getRuleInfo`. Handles ESM rules.
*/
function getRuleExportsESM (ast) {
return ast.body
.filter(statement => statement.type === 'ExportDefaultDeclaration')
.map(statement => statement.declaration)
.reduce((currentExports, node) => {
if (node.type === 'ObjectExpression') {
return node.properties.reduce((parsedProps, prop) => {
const keyValue = module.exports.getKeyName(prop);
if (INTERESTING_RULE_KEYS.has(keyValue)) {
parsedProps[keyValue] = prop.value;
}
return parsedProps;
}, {});
} else if (isNormalFunctionExpression(node)) {
return { create: node, meta: null, isNewStyle: false };
}
return currentExports;
}, {});
}

/**
* Helper for `getRuleInfo`. Handles CJS rules.
*/
function getRuleExportsCJS (ast) {
let exportsVarOverridden = false;
let exportsIsFunction = false;
return ast.body
.filter(statement => statement.type === 'ExpressionStatement')
.map(statement => statement.expression)
.filter(expression => expression.type === 'AssignmentExpression')
.filter(expression => expression.left.type === 'MemberExpression')
.reduce((currentExports, node) => {
if (
node.left.object.type === 'Identifier' && node.left.object.name === 'module' &&
node.left.property.type === 'Identifier' && node.left.property.name === 'exports'
) {
exportsVarOverridden = true;
if (isNormalFunctionExpression(node.right)) {
// Check `module.exports = function () {}`

exportsIsFunction = true;
return { create: node.right, meta: null, isNewStyle: false };
} else if (node.right.type === 'ObjectExpression') {
// Check `module.exports = { create: function () {}, meta: {} }`

return node.right.properties.reduce((parsedProps, prop) => {
const keyValue = module.exports.getKeyName(prop);
if (INTERESTING_RULE_KEYS.has(keyValue)) {
parsedProps[keyValue] = prop.value;
}
return parsedProps;
}, {});
}
return {};
} else if (
!exportsIsFunction &&
node.left.object.type === 'MemberExpression' &&
node.left.object.object.type === 'Identifier' && node.left.object.object.name === 'module' &&
node.left.object.property.type === 'Identifier' && node.left.object.property.name === 'exports' &&
node.left.property.type === 'Identifier' && INTERESTING_RULE_KEYS.has(node.left.property.name)
) {
// Check `module.exports.create = () => {}`

currentExports[node.left.property.name] = node.right;
} else if (
!exportsVarOverridden &&
node.left.object.type === 'Identifier' && node.left.object.name === 'exports' &&
node.left.property.type === 'Identifier' && INTERESTING_RULE_KEYS.has(node.left.property.name)
) {
// Check `exports.create = () => {}`

currentExports[node.left.property.name] = node.right;
}
return currentExports;
}, {});
}

module.exports = {
/**
* Performs static analysis on an AST to try to determine the final value of `module.exports`.
* @param {{ast: ASTNode, scopeManager?: ScopeManager}} sourceCode The object contains `Program` AST node, and optional `scopeManager`
Expand All @@ -94,84 +175,7 @@ module.exports = {
from the file, the return value will be `null`.
*/
getRuleInfo ({ ast, scopeManager }) {
const INTERESTING_KEYS = new Set(['create', 'meta']);
let exportsVarOverridden = false;
let exportsIsFunction = false;

// ESM
const exportNodesESM = ast.body
.filter(statement => statement.type === 'ExportDefaultDeclaration')
.map(statement => statement.declaration)
.reduce((currentExports, node) => {
if (node.type === 'ObjectExpression') {
return node.properties.reduce((parsedProps, prop) => {
const keyValue = module.exports.getKeyName(prop);
if (INTERESTING_KEYS.has(keyValue)) {
parsedProps[keyValue] = prop.value;
}
return parsedProps;
}, {});
} else if (isNormalFunctionExpression(node)) {
exportsIsFunction = true;
return { create: node, meta: null };
}
return currentExports;
}, null);

// CJS
const exportNodesCJS = ast.body
.filter(statement => statement.type === 'ExpressionStatement')
.map(statement => statement.expression)
.filter(expression => expression.type === 'AssignmentExpression')
.filter(expression => expression.left.type === 'MemberExpression')
.reduce((currentExports, node) => {
if (
node.left.object.type === 'Identifier' && node.left.object.name === 'module' &&
node.left.property.type === 'Identifier' && node.left.property.name === 'exports'
) {
exportsVarOverridden = true;

if (isNormalFunctionExpression(node.right)) {
// Check `module.exports = function () {}`

exportsIsFunction = true;
return { create: node.right, meta: null };
} else if (node.right.type === 'ObjectExpression') {
// Check `module.exports = { create: function () {}, meta: {} }`

exportsIsFunction = false;
return node.right.properties.reduce((parsedProps, prop) => {
const keyValue = module.exports.getKeyName(prop);
if (INTERESTING_KEYS.has(keyValue)) {
parsedProps[keyValue] = prop.value;
}
return parsedProps;
}, {});
}
return {};
} else if (
!exportsIsFunction &&
node.left.object.type === 'MemberExpression' &&
node.left.object.object.type === 'Identifier' && node.left.object.object.name === 'module' &&
node.left.object.property.type === 'Identifier' && node.left.object.property.name === 'exports' &&
node.left.property.type === 'Identifier' && INTERESTING_KEYS.has(node.left.property.name)
) {
// Check `module.exports.create = () => {}`

currentExports[node.left.property.name] = node.right;
} else if (
!exportsVarOverridden &&
node.left.object.type === 'Identifier' && node.left.object.name === 'exports' &&
node.left.property.type === 'Identifier' && INTERESTING_KEYS.has(node.left.property.name)
) {
// Check `exports.create = () => {}`

currentExports[node.left.property.name] = node.right;
}
return currentExports;
}, {});

const exportNodes = exportNodesESM || exportNodesCJS;
const exportNodes = ast.sourceType === 'module' ? getRuleExportsESM(ast) : getRuleExportsCJS(ast);

const createExists = Object.prototype.hasOwnProperty.call(exportNodes, 'create');
if (!createExists) {
Expand All @@ -185,7 +189,7 @@ module.exports = {
return null;
}

return Object.assign({ isNewStyle: !exportsIsFunction, meta: null }, exportNodes);
return Object.assign({ isNewStyle: true, meta: null }, exportNodes);
},

/**
Expand Down

0 comments on commit 7a12707

Please sign in to comment.