UNPKG

lively.ast

Version:

Parsing JS code into ASTs and tools to query and transform these trees.

218 lines (178 loc) 6.4 kB
import { parse } from "../lib/parser.js"; import { arr, Path, string, obj } from "lively.lang"; /* types found: The def data structure: {type, name, node, children?, parent?} class-decl class-constructor class-instance-method class-class-method class-instance-getter class-instance-setter class-class-getter class-class-setter function-decl var-decl object-decl object-method object-property */ // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // main method // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- export function findDecls(parsed, options) { // lively.debugNextMethodCall(lively.ast.codeCategorizer, "findDecls") options = options || obj.merge({hideOneLiners: false}, options); if (typeof parsed === "string") parsed = parse(parsed, {addSource: true}); var topLevelNodes = parsed.type === "Program" ? parsed.body : parsed.body.body, defs = [], hideOneLiners = options.hideOneLiners && parsed.source; for (let node of topLevelNodes) { node = unwrapExport(node); var found = functionWrapper(node, options) || varDefs(node) || funcDef(node) || es6ClassDef(node) || someObjectExpressionCall(node); if (!found) continue; if (options.hideOneLiners) { if (parsed.loc) { found = found.filter(def => !def.node.loc || (def.node.loc.start.line !== def.node.loc.end.line)); } else if (parsed.source) { var filtered = []; for (let def of found) { if ((def.parent && filtered.includes(def.parent)) // parent is in || (def.node.source || "").includes("\n") // more than one line ) filtered.push(def); } found = filtered; } } defs.push(...found); } return defs; } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // defs // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- function es6ClassDef(node) { if (node.type !== "ClassDeclaration") return null; var def = { type: "class-decl", name: node.id.name, node: node, children: [] }; def.children.push(...node.body.body.map((node, i) => es6ClassMethod(node, def, i)) .filter(Boolean)) return [def, ...def.children] } function es6ClassMethod(node, parent, i) { if (node.type !== "MethodDefinition") return null; var type; if (node.kind === "constructor") type = "class-constructor"; else if (node.kind === "method") type = node.static ? "class-class-method" : "class-instance-method"; else if (node.kind === "get") type = node.static ? "class-class-getter" : "class-instance-getter"; else if (node.kind === "set") type = node.static ? "class-class-setter" : "class-instance-setter"; return type ? { type, parent, node, name: node.key.name, } : null; } function varDefs(node) { if (node.type !== "VariableDeclaration") return null; var result = []; for (let {id, node} of withVarDeclIds(node)) { var def = {name: id.name, node: node, type: "var-decl"}; result.push(def); if (!def.node.init) continue; var node = def.node.init; while (node.type === "AssignmentExpression") node = node.right; if (node.type === "ObjectExpression") { def.type = "object-decl"; def.children = objectKeyValsAsDefs(node).map(ea => ({...ea, type: "object-" + ea.type, parent: def})); result.push(...def.children); continue; } var objDefs = someObjectExpressionCall(node, def); if (objDefs) { def.children = objDefs.map(d => ({...d, parent: def})); result.push(...def.children) } } return result; } function funcDef(node) { if (node.type !== "FunctionStatement" && node.type !== "FunctionDeclaration") return null; return [{name: node.id.name, node, type: "function-decl"}]; } function someObjectExpressionCall(node, parentDef) { // like Foo({....}) if (node.type === "ExpressionStatement") node = node.expression; if (node.type !== "CallExpression") return null; var objArg = node.arguments.find(a => a.type === "ObjectExpression"); if (!objArg) return null; return objectKeyValsAsDefs(objArg, parentDef); } function functionWrapper(node, options) { if (!isFunctionWrapper(node)) return null; var decls; // Is it a function wrapper passed as arg? // like ;(function(run) {... })(function(exports) {...}) var argFunc = Path("expression.arguments.0").get(node); if (argFunc && argFunc.type === "FunctionExpression" && string.lines(argFunc.source || "").length > 5) { // lively.debugNextMethodCall(lively.ast.CodeCategorizer, "findDecls"); decls = findDecls(argFunc, options); } else { decls = findDecls(Path("expression.callee").get(node), options); } var parent = {node: node, name: Path("expression.callee.id.name").get(node)}; decls.forEach(function(decl) { return decl.parent || (decl.parent = parent) }); return decls; } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // helpers // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- function unwrapExport(node) { return (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") && node.declaration ? node.declaration : node; } function objectKeyValsAsDefs(objectExpression, parent) { return objectExpression.properties.map(node => ({ name: node.key.name || node.key.value, type: node.value.type === "FunctionExpression" ? "method" : "property", node, parent })); } function isFunctionWrapper(node) { return Path("expression.type").get(node) === "CallExpression" && Path("expression.callee.type").get(node) === "FunctionExpression"; } function declIds(idNodes) { return arr.flatmap(idNodes, function(ea) { if (!ea) return []; if (ea.type === "Identifier") return [ea]; if (ea.type === "RestElement") return [ea.argument]; if (ea.type === "ObjectPattern") return declIds(arr.pluck(ea.properties, "value")); if (ea.type === "ArrayPattern") return declIds(ea.elements); return []; }); } function withVarDeclIds(varNode) { return varNode.declarations.map(declNode => { if (!declNode.source && declNode.init) declNode.source = declNode.id.name + " = " + declNode.init.source return {node: declNode, id: declNode.id}; }); }