Created
September 3, 2018 19:48
-
-
Save colxi/c0bc24a3ef945a1248fe442c80fdb777 to your computer and use it in GitHub Desktop.
Extract keypaths from expression AST (abstract syntax tree)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// works with AST generated by esprima or jsep | |
const Parser = { | |
// 'global' for Node, or any other value for custom scenaros | |
outerscope : 'window', | |
getObservablePaths : function(expression) { | |
console.log('Parser.getPaths',expression); | |
var ast = esprima.parse(expression); | |
if (ast) { | |
console.log('Parser.getPaths',ast); | |
var paths = new Array(); | |
this.recurseObservablePaths(ast,paths); | |
return paths; | |
} else return false; | |
}, | |
recurseObservablePaths : function(tree,paths,path) { | |
if (!tree || !paths) return false; | |
if (tree.type =='Identifier') { | |
// some sort of global | |
console.log('Parser.recurseObservablePaths','adding identifier '+tree.name); | |
paths.push({object:this.outerscope,path:tree.name}); | |
} else if (tree.type =='MemberExpression') { | |
// member expression | |
if (tree.property.type=='Identifier' || tree.property.type=='Literal') { | |
// like foo[bar][24].quz ; the property is 'quz' | |
// dabble down the object to get the path | |
if (tree.property.type=='Identifier') { | |
path = (path)?'.'+tree.property.name+path:'.'+tree.property.name; | |
} else { | |
path = (path)?'['+tree.property.raw+']'+path:'['+tree.property.raw+']'; | |
} | |
if (tree.object.type=='Identifier') { | |
// like foo.bar ; were done with this path - push ! | |
console.log('Parser.recurseObservablePaths','adding path '+tree.object.name+path); | |
if (path.indexOf('.')===0) { | |
paths.push({object:tree.object.name,path:path.substring(1)}); | |
} else { | |
paths.push({object:this.outerscope,path:tree.object.name+path}); | |
} | |
} else { | |
if (tree.object.type=='MemberExpression') { | |
// like foo.bar.quz ; recurse the object | |
console.log('Parser.recurseObservablePaths','recursing member expression ..'); | |
this.recurseObservablePaths(tree.object,paths,path); | |
} else { | |
// like foo(bar).quz ; the object is something weird. | |
// ignore the property .. but recurse the object | |
this.recurseObservablePaths(tree.object,paths); | |
} | |
} | |
} else { | |
// the property is some sort of thing itself: | |
if (tree.object.type=='Identifier') { | |
// like foo[bar.quz] - push the object, recurse the property | |
console.log('Parser.recurseObservablePaths','adding identifier '+tree.object.name); | |
paths.push({object:this.outerscope,path:tree.object.name}); | |
this.recurseObservablePaths(tree.property); | |
} else { | |
// like foo.bar[quz(raz)] ; recurse both | |
console.log('Parser.recurseObservablePaths','recursing member expression ..'); | |
this.recurseObservablePaths(tree.object,paths); | |
this.recurseObservablePaths(tree.property,paths); | |
} | |
} | |
} else if (tree.type=="CallExpression") { | |
// like foo.bar(quz.baz) ; we only want the arguments | |
this.recurseObservablePaths(tree.arguments,paths); | |
} else if (tree.type=="AssignmentExpression") { | |
// like foo.bar=baz*quz ; we only want the right hand | |
this.recurseObservablePaths(tree.right,paths); | |
} else { | |
// unknown garbage. dig deeper. | |
var props = Object.getOwnPropertyNames(tree); | |
for (var pc=0; pc<props.length; pc++) { | |
var key = props[pc]; | |
if (typeof tree[key] == 'object') { | |
if (Array.isArray(tree[key])) { | |
for (var kc=0;kc<tree[key].length;kc++) { | |
console.log('Parser.recurseObservablePaths','recursing '+key+':'+kc); | |
this.recurseObservablePaths(tree[key][kc],paths); | |
} | |
} else { | |
console.log('Parser.recurseObservablePaths','recursing '+key); | |
this.recurseObservablePaths(tree[key],paths); | |
} | |
} else { | |
console.log('Parser.recurseObservablePaths','ignoring '+key); | |
} | |
} | |
} | |
} | |
} | |
// test: | |
Parser.getObservablePaths('myObj.myNested.arr[ myObject.myProp]') | |
/* | |
returns : [ | |
{object:'myObj', keys:'myNested.arr'}, | |
{object:'myObject', keys:'myProp'} | |
] | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment