Skip to content

Commit

Permalink
Find exports for underscore.js
Browse files Browse the repository at this point in the history
Because of how their exported module is constructed, finding named
exports turned out to be a little bit of a challenge. But through a
combination of remembering the defined objects, plus using a regexp to
find the actual `module.exports`, I was able to get it working.

Because this ended up being a little brittle, I added a regression test
for a bug that I was close to introducing.
  • Loading branch information
trotzig committed Jan 21, 2017
1 parent c8641b0 commit 12f4d7b
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 14 deletions.
43 changes: 43 additions & 0 deletions lib/__tests__/findExports-test.js
Expand Up @@ -225,3 +225,46 @@ module.exports = React;
hasDefault: true,
});
});

it('finds underscore exports', () => {
expect(findExports(`
(function() {
var _ = function() {};
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
_.debounce = function() {};
_.pluck = function() {};
})();
`)).toEqual({
named: [
'debounce',
'pluck',
],
hasDefault: true,
});
});

it('does not fail for inner exports that are not objects', () => {
expect(findExports(`
(function() {
var snowden = '';
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = snowden;
}
}
})();
`)).toEqual({
named: [],
hasDefault: true,
});
});
44 changes: 30 additions & 14 deletions lib/findExports.js
Expand Up @@ -101,20 +101,20 @@ function findDefinedNames(node, definedNames) {
return;
}
node.declarations.forEach(({ id, init }) => {
if (!init || init.type !== 'ObjectExpression') {
if (!init) {
return;
}
definedNames[id.name] = // eslint-disable-line no-param-reassign
init.properties.map(({ key }) => key.name).filter(Boolean);
if (init.type === 'ObjectExpression') {
definedNames[id.name] = // eslint-disable-line no-param-reassign
init.properties.map(({ key }) => key.name).filter(Boolean);
} else if (init.type === 'FunctionExpression') {
definedNames[id.name] = []; // eslint-disable-line no-param-reassign
}
});
}

function findNamedExports(nodes, absPathToFile) {
function findNamedExports(nodes, absPathToFile, definedNames) {
const result = [];
const definedNames = {};
nodes.forEach((node) => {
findDefinedNames(node, definedNames);
});
nodes.forEach((node) => {
result.push(...findESNamedExports(node));
result.push(...findCommonJSExports(node, definedNames,
Expand Down Expand Up @@ -148,9 +148,13 @@ function hasDefaultExport(nodes) {
});
}

const DEFAULT_EXPORT_MATCH = /\smodule\.exports\s*=/;
function hasRawModuleExportsMatch(data) {
return DEFAULT_EXPORT_MATCH.test(data);
const DEFAULT_EXPORT_PATTERN = /\smodule\.exports\s*=\s*(\w+)/;
function findRawModuleExportIdentifier(data) {
const match = data.match(DEFAULT_EXPORT_PATTERN);
if (match) {
return match[1];
}
return undefined;
}

function findRootNodes(ast) {
Expand Down Expand Up @@ -179,9 +183,21 @@ function findRootNodes(ast) {
export default function findExports(data, absPathToFile) {
const ast = parse(data);
const rootNodes = findRootNodes(ast);
const definedNames = {};
rootNodes.forEach((node) => {
findDefinedNames(node, definedNames);
});
const named = findNamedExports(rootNodes, absPathToFile, definedNames);
let hasDefault = hasDefaultExport(rootNodes);
if (!hasDefault) {
const rawExportedId = findRawModuleExportIdentifier(data);
hasDefault = !!rawExportedId;
if (!named.length) {
named.push(...(definedNames[rawExportedId] || []));
}
}
return {
named: findNamedExports(rootNodes, absPathToFile),
hasDefault: hasDefaultExport(rootNodes) ||
hasRawModuleExportsMatch(data),
named,
hasDefault,
};
}

0 comments on commit 12f4d7b

Please sign in to comment.