@gerhobbelt/mathjax-third-party-extensions
Version:
A list of MathJax extensions provided by third-party contributors
446 lines (403 loc) • 17.3 kB
JavaScript
// convertToFunctionExpression.js
// ==============================
// Returns either an IIFE or variable declaration.
// Internally calls either convertToIIFE() or convertToIIFEDeclaration()
define([
'utils',
'convertToIIFE',
'convertToIIFEDeclaration',
'defaultValues',
'normalizeModuleName',
'defaultValues'
], function(
utils,
convertToIIFE,
convertToIIFEDeclaration,
defaultValues,
normalizeModuleName,
defaultValues
) {
return function convertToFunctionExpression(obj) {
var amdclean = this,
options = amdclean.options,
ignoreModules = options.ignoreModules,
node = obj.node,
isDefine = obj.isDefine,
isRequire = obj.isRequire,
isOptimized = false,
moduleName = obj.moduleName,
moduleId = obj.moduleId,
dependencies = obj.dependencies,
depLength = dependencies.length,
aggressiveOptimizations = options.aggressiveOptimizations,
exportsExpressions = [],
moduleExportsExpressions = [],
defaultRange = defaultValues.defaultRange,
defaultLOC = defaultValues.defaultLOC,
range = obj.range || defaultRange,
loc = obj.loc || defaultLOC,
shouldOptimize = obj.shouldOptimize,
dependencyBlacklist = defaultValues.dependencyBlacklist,
hasNonMatchingParameter = false,
callbackFunc = (function() {
var callbackFunc = obj.moduleReturnValue,
body,
returnStatements,
firstReturnStatement,
returnStatementArg;
// If the module callback function is not empty
if (callbackFunc && callbackFunc.type === 'FunctionExpression' && callbackFunc.body && _.isArray(callbackFunc.body.body) && callbackFunc.body.body.length) {
// Filter 'use strict' statements
body = _.filter(callbackFunc.body.body, function(node) {
if (options.removeUseStricts === true) {
return !utils.isUseStrict(node.expression);
} else {
return node;
}
});
callbackFunc.body.body = body;
// Returns an array of all return statements
returnStatements = _.where(body, {
'type': 'ReturnStatement'
});
exportsExpressions = _.where(body, {
'left': {
'type': 'Identifier',
'name': 'exports'
}
});
moduleExportsExpressions = _.where(body, {
'left': {
'type': 'MemberExpression',
'object': {
'type': 'Identifier',
'name': 'module'
},
'property': {
'type': 'Identifier',
'name': 'exports'
}
}
});
// If there is a return statement
if (returnStatements.length) {
firstReturnStatement = returnStatements[0];
returnStatementArg = firstReturnStatement.argument;
hasNonMatchingParameter = function() {
var nonMatchingParameter = false;
_.each(callbackFunc.params, function(currentParam) {
var currentParamName = currentParam.name;
if (!amdclean.storedModules[currentParamName] && !dependencyBlacklist[currentParamName]) {
nonMatchingParameter = true;
}
});
return nonMatchingParameter;
}();
// If something other than a function expression is getting returned
// and there is more than one AST child node in the factory function
// return early
if (hasNonMatchingParameter || !shouldOptimize || (!utils.isFunctionExpression(firstReturnStatement) && body.length > 1) || (returnStatementArg && returnStatementArg.type === 'Identifier')) {
return callbackFunc;
} else {
// Optimize the AMD module by setting the callback function to the return statement argument
callbackFunc = returnStatementArg;
isOptimized = true;
if (callbackFunc.params) {
depLength = callbackFunc.params.length;
}
}
}
} else if (callbackFunc && callbackFunc.type === 'FunctionExpression' && callbackFunc.body && _.isArray(callbackFunc.body.body) && callbackFunc.body.body.length === 0) {
callbackFunc = {
'type': 'Identifier',
'name': 'undefined',
'range': range,
'loc': loc
};
depLength = 0;
}
return callbackFunc;
}()),
hasReturnStatement = (function() {
var returns = [];
if (callbackFunc && callbackFunc.body && _.isArray(callbackFunc.body.body)) {
returns = _.where(callbackFunc.body.body, {
'type': 'ReturnStatement'
});
if (returns.length) {
return true;
}
}
return false;
}()),
originalCallbackFuncParams,
hasExportsParam = (function() {
var cbParams = callbackFunc.params || [];
return _.where(cbParams, {
'name': 'exports'
}).length;
}()),
hasModuleParam = (function() {
var cbParams = callbackFunc.params || [];
return _.where(cbParams, {
'name': 'module'
}).length;
}()),
normalizeDependencyNames = {},
dependencyNames = (function() {
var deps = [],
currentName;
_.each(dependencies, function(currentDependency) {
currentName = normalizeModuleName.call(amdclean, utils.normalizeDependencyName(moduleId, currentDependency), moduleId);
normalizeDependencyNames[currentName] = true;
deps.push({
'type': 'Identifier',
'name': currentName,
'range': defaultRange,
'loc': defaultLOC
});
});
return deps;
}()),
// Makes sure the new name is not an existing callback function dependency and/or existing local variable
findNewParamName = function findNewParamName(name) {
name = '_' + name + '_';
var containsLocalVariable = (function() {
var containsVariable = false;
if (normalizeDependencyNames[name]) {
containsVariable = true;
} else {
estraverse.traverse(callbackFunc, {
'enter': function(node) {
if (node.type === 'VariableDeclarator' &&
node.id &&
node.id.type === 'Identifier' &&
node.id.name === name) {
containsVariable = true;
}
}
});
}
return containsVariable;
}());
// If there is not a local variable declaration with the passed name, return the name and surround it with underscores
// Else if there is already a local variable declaration with the passed name, recursively add more underscores surrounding it
if (!containsLocalVariable) {
return name;
} else {
return findNewParamName(name);
}
},
matchingRequireExpressionNames = (function() {
var matchingNames = [];
if (hasExportsParam) {
estraverse.traverse(callbackFunc, {
'enter': function(node) {
var variableName,
expressionName;
if (node.type === 'VariableDeclarator' && utils.isRequireExpression(node.init)) {
// If both variable name and expression names are there
if (node.id && node.id.name && node.init && node.init['arguments'] && node.init['arguments'][0] && node.init['arguments'][0].value) {
variableName = node.id.name;
expressionName = normalizeModuleName.call(amdclean, utils.normalizeDependencyName(moduleId, node.init['arguments'][0].value, moduleId));
if (!_.contains(ignoreModules, expressionName) && (variableName === expressionName)) {
matchingNames.push({
'originalName': expressionName,
'newName': findNewParamName(expressionName),
'range': (node.range || defaultRange),
'loc': (node.loc || defaultLOC)
});
}
}
}
}
});
}
return matchingNames;
}()),
matchingRequireExpressionParams = (function() {
var params = [];
_.each(matchingRequireExpressionNames, function(currentParam) {
params.push({
'type': 'Identifier',
'name': currentParam.newName ? currentParam.newName : currentParam,
'range': currentParam.range,
'loc': currentParam.loc
});
});
return params;
}()),
callbackFuncParams = (function() {
var deps = [],
currentName,
cbParams = _.union((callbackFunc.params && callbackFunc.params.length ? callbackFunc.params : !shouldOptimize && dependencyNames && dependencyNames.length ? dependencyNames : []), matchingRequireExpressionParams),
mappedParameter = {},
// For calculating cbParams we'll optimize by removing not referenced names in the callback parameters.
// If the callback body contains a reference to 'arguments' then we cannot perform this optimization.
// but at the same time if only inner functions body contains arguments WE DO optimize.
// What we do is find inner function declarations and then remove their text from the callback body. Then we look for 'arguments' references
innerFunctions = [],
lookForArgumentsCode;
if (callbackFunc.body) {
estraverse.traverse(callbackFunc.body, {
enter: function (node, parent) {
if (node.type == 'FunctionExpression' || node.type == 'FunctionDeclaration')
innerFunctions.push(node);
}
});
if (innerFunctions.length) {
for (var i = callbackFunc.body.range[0]; i < callbackFunc.body.range[1]; i++) {
_.each(innerFunctions, function (innerFunction) {
if (i<innerFunction.range[0] || i>=innerFunction.range[1]) {
lookForArgumentsCode += amdclean.options.code[i];
}
});
}
} else {
lookForArgumentsCode = amdclean.options.code.substring(callbackFunc.body.range[0], callbackFunc.body.range[1]);
}
if (/[^\w0-9_]arguments[^\w0-9_]/.test(lookForArgumentsCode)) {
cbParams = cbParams.concat(dependencyNames.slice(cbParams.length));
}
}
_.each(cbParams, function(currentParam, iterator) {
if (currentParam) {
currentName = currentParam.name;
} else {
currentName = dependencyNames[iterator].name;
}
if (!shouldOptimize && currentName !== '{}') {
deps.push({
'type': 'Identifier',
'name': currentName,
'range': defaultRange,
'loc': defaultLOC
});
} else if (currentName !== '{}' && (!hasExportsParam || defaultValues.dependencyBlacklist[currentName] !== 'remove')) {
deps.push({
'type': 'Identifier',
'name': currentName,
'range': defaultRange,
'loc': defaultLOC
});
// If a callback parameter is not the exact name of a stored module and there is a dependency that matches the current callback parameter
if (!isOptimized && aggressiveOptimizations === true && !amdclean.storedModules[currentName] && dependencyNames[iterator]) {
// If the current dependency has not been stored
if (!amdclean.callbackParameterMap[dependencyNames[iterator].name]) {
amdclean.callbackParameterMap[dependencyNames[iterator].name] = [{
'name': currentName,
'count': 1
}];
} else {
mappedParameter = _.where(amdclean.callbackParameterMap[dependencyNames[iterator].name], {
'name': currentName
});
if (mappedParameter.length) {
mappedParameter = mappedParameter[0];
mappedParameter.count += 1;
} else {
amdclean.callbackParameterMap[dependencyNames[iterator].name].push({
'name': currentName,
'count': 1
});
}
}
}
}
});
originalCallbackFuncParams = deps;
// Only return callback function parameters that do not directly match the name of existing stored modules
return _.filter(deps || [], function(currentParam) {
return aggressiveOptimizations === true && shouldOptimize ? !amdclean.storedModules[currentParam.name] : true;
});
}()),
isCommonJS = !hasReturnStatement && hasExportsParam,
hasExportsAssignment = exportsExpressions.length || moduleExportsExpressions.length,
dependencyNameLength,
callbackFuncParamsLength;
// Only return dependency names that do not directly match the name of existing stored modules
dependencyNames = _.filter(dependencyNames || [], function(currentDep, iterator) {
var mappedCallbackParameter = originalCallbackFuncParams[iterator],
currentDepName = currentDep.name;
// If the matching callback parameter matches the name of a stored module, then do not return it
// Else if the matching callback parameter does not match the name of a stored module, return the dependency
return aggressiveOptimizations === true && shouldOptimize ? (!mappedCallbackParameter || amdclean.storedModules[mappedCallbackParameter.name] && mappedCallbackParameter.name === currentDepName ? !amdclean.storedModules[currentDepName] : !amdclean.storedModules[mappedCallbackParameter.name]) : true;
});
dependencyNames = _.map(dependencyNames || [], function(currentDep, iterator) {
if (dependencyBlacklist[currentDep.name]) {
currentDep.name = '{}';
}
return currentDep;
});
dependencyNameLength = dependencyNames.length;
callbackFuncParamsLength = callbackFuncParams.length;
// If the module dependencies passed into the current module are greater than the used callback function parameters, do not pass the dependencies
if (dependencyNameLength > callbackFuncParamsLength) {
dependencyNames.splice(callbackFuncParamsLength, dependencyNameLength - callbackFuncParamsLength);
}
// If it is a CommonJS module and there is an exports assignment, make sure to return the exports object
if (isCommonJS && hasExportsAssignment) {
callbackFunc.body.body.push({
'type': 'ReturnStatement',
'argument': {
'type': 'Identifier',
'name': 'exports',
'range': defaultRange,
'loc': defaultLOC
},
'range': defaultRange,
'loc': defaultLOC
});
}
// Makes sure to update all the local variable require expressions to any updated names
estraverse.replace(callbackFunc, {
'enter': function(node) {
var normalizedModuleName,
newName;
if (utils.isRequireExpression(node)) {
if (node['arguments'] && node['arguments'][0] && node['arguments'][0].value) {
normalizedModuleName = normalizeModuleName.call(amdclean, utils.normalizeDependencyName(moduleId, node['arguments'][0].value, moduleId));
if (_.contains(ignoreModules, normalizedModuleName)) {
return node;
}
if (_.where(matchingRequireExpressionNames, {
'originalName': normalizedModuleName
}).length) {
newName = _.where(matchingRequireExpressionNames, {
'originalName': normalizedModuleName
})[0].newName;
}
return {
'type': 'Identifier',
'name': newName ? newName : normalizedModuleName,
'range': (node.range || defaultRange),
'loc': (node.loc || defaultLOC)
};
} else {
return node;
}
}
}
});
if (isDefine) {
return convertToIIFEDeclaration.call(amdclean, {
'moduleId': moduleId,
'moduleName': moduleName,
'dependencyNames': dependencyNames,
'callbackFuncParams': callbackFuncParams,
'hasModuleParam': hasModuleParam,
'hasExportsParam': hasExportsParam,
'callbackFunc': callbackFunc,
'isOptimized': isOptimized,
'node': node
});
} else if (isRequire) {
return convertToIIFE.call(amdclean, {
'dependencyNames': dependencyNames,
'callbackFuncParams': callbackFuncParams,
'callbackFunc': callbackFunc,
'node': node
});
}
};
});