work.suroh.tk/node_modules/detective-amd/index.js

124 lines
3.5 KiB
JavaScript

'use strict';
const Walker = require('node-source-walk');
const types = require('ast-module-types');
const escodegen = require('escodegen');
const getModuleType = require('get-amd-module-type');
/**
* @param {String} src - the string content or AST of an AMD module
* @param {Object} [options]
* @param {Object} [options.skipLazyLoaded] - whether or not to omit inner (non-REM) required dependencies
* @return {String[]} List of partials/dependencies referenced in the given file
*/
module.exports = function(src, options = {}) {
let dependencies = [];
const walker = new Walker();
if (typeof src === 'undefined') { throw new Error('src not given'); }
if (src === '') { return dependencies; }
walker.walk(src, function(node) {
if (!types.isTopLevelRequire(node) &&
!types.isDefine(node) &&
!types.isRequire(node)) {
return;
}
const type = getModuleType.fromAST(node);
if (!types.isTopLevelRequire(node) && types.isRequire(node) && type !== 'rem' && options.skipLazyLoaded) {
return;
}
const deps = getDependencies(node, type, options);
if (deps.length) {
dependencies = dependencies.concat(deps);
}
});
// Avoid duplicates
return dependencies.filter(function(dep, idx) {
return dependencies.indexOf(dep) === idx;
});
};
/**
* @param {Object} node - AST node
* @param {String} type - sniffed type of the module
* @param {Object} options - detective configuration
* @returns {String[]} A list of file dependencies or an empty list if the type is unsupported
*/
function getDependencies(node, type, options) {
// Note: No need to handle nodeps since there won't be any dependencies
switch (type) {
case 'named':
var args = node.arguments || [];
return getElementValues(args[1]).concat(options.skipLazyLoaded ? [] : getLazyLoadedDeps(node));
case 'deps':
case 'driver':
var args = node.arguments || [];
return getElementValues(args[0]).concat(options.skipLazyLoaded ? [] : getLazyLoadedDeps(node));
case 'factory':
case 'rem':
// REM inner requires aren't really "lazy loaded," but the form is the same
return getLazyLoadedDeps(node);
}
return [];
}
/**
* Looks for dynamic module loading
*
* @param {AST} node
* @return {String[]} List of dynamically required dependencies
*/
function getLazyLoadedDeps(node) {
// Use logic from node-detective to find require calls
const walker = new Walker();
let dependencies = [];
walker.traverse(node, function(innerNode) {
if (types.isRequire(innerNode)) {
const requireArgs = innerNode.arguments;
if (!requireArgs.length) { return; }
// Either require('x') or require(['x'])
const deps = requireArgs[0];
if (deps.type === 'ArrayExpression') {
dependencies = dependencies.concat(getElementValues(deps));
} else {
dependencies.push(getEvaluatedValue(deps));
}
}
});
return dependencies;
}
/**
* @param {Object} nodeArguments
* @returns {String[]} the literal values from the passed array
*/
function getElementValues(nodeArguments) {
const elements = nodeArguments.elements || [];
return elements.map(function(el) {
return getEvaluatedValue(el);
}).filter(Boolean);
}
/**
* @param {AST} node
* @returns {String} the statement represented by AST node
*/
function getEvaluatedValue(node) {
if (node.type === 'Literal' || node.type === 'StringLiteral') { return node.value; }
if (node.type === 'CallExpression') { return ''; }
return escodegen.generate(node);
}