"use strict"; let expressions = [ function foo({id, other_prop}) { return (other_prop < 34 && other_prop >= 2 && id !== "foo"); }, (data) => { let id = data.id; let someProp = data.prop; let otherProp = someProp; if (id !== "foo") { if (someProp < 34) { return (otherProp >= 2); } else { return false; } } }, ({foo: bar}) => bar > 4, function (data) { let {foo} = data; return (foo < 4); }, 42, "bar" ]; const util = require("util"); const acorn = require("acorn"); const estreeAssignParent = require("estree-assign-parent"); const scopeAnalyzer = require("scope-analyzer"); const astw = require("astw"); function logAst(ast, depth = 7) { return util.inspect(ast, {colors: true, depth: depth}); } function analyzeFunction(func) { let source = func.toString(); console.log(source); console.log(""); /* The below is a hack to make anonymous functions parse correctly. */ let ast = acorn.parse(`[${source}]`); let functionAst = ast.body[0].expression.elements[0]; estreeAssignParent(functionAst); scopeAnalyzer.crawl(functionAst); let functionArg = functionAst.params[0]; if (functionArg.type === "ObjectPattern") { for (let property of functionArg.properties) { // key = from prop, value = to var let sourcePropertyName = property.key.name; property.value._sourceProp = sourcePropertyName; } } else if (functionArg.type === "Identifier") { functionArg._dataSource = true; } else { throw new Error(`Encountered unrecognized function argument type: ${functionArg.type}`); } let walk = astw(functionAst); walk((node) => { if (node.type === "VariableDeclarator") { if (node.id.type === "ObjectPattern") { if (node.init.type === "Identifier" && scopeAnalyzer.getBinding(node.init).definition._dataSource === true) { for (let property of node.id.properties) { property.value._sourceProp = property.key.name; } } } else if (node.id.type === "Identifier") { if (node.init.type === "MemberExpression" && scopeAnalyzer.getBinding(node.init.object).definition._dataSource === true) { node.id._sourceProp = node.init.property.name; } else if (node.init.type === "Identifier") { let definition = scopeAnalyzer.getBinding(node.init).definition; if (definition._sourceProp != null) { node.id._sourceProp = definition._sourceProp; } } } else { throw new Error(`Encountered unrecognized assignment id type: ${node.id.type}`); } } }); let walkBody = astw(functionAst.body); function getDataProperty(node) { if (node.type === "Identifier" && node.parent.type !== "MemberExpression") { let binding = scopeAnalyzer.getBinding(node); if (binding != null) { let definition = binding.definition; if (definition != null) { if (definition._dataSource !== true) { if (definition._sourceProp != null) { return definition._sourceProp; } else { throw new Error(`Could not connect variable '${node.name}' to a data property`); } } } else { throw new Error(`Encountered undefined variable: ${node.name}`); } } else { throw new Error(`Encountered undefined variable: ${node.name}`); } } else { throw new Error("Node is not a free-standing (variable) identifier"); } } walkBody((node) => { if (node.type === "Identifier" && node.parent.type !== "MemberExpression") { let sourceProperty = getDataProperty(node); if (sourceProperty != null) { console.log(`Variable '${node.name}' is derived from data property '${sourceProperty}'`); } } }); // console.log(util.inspect(functionAst, {depth: 7, colors: true})); } for (let expression of expressions) { if (typeof expression === "function") { let result = analyzeFunction(expression); // console.log(expression.toString()); } else { console.log(`!! ${expression} is not a function`); } console.log("\n=\n"); }