How to use dependency injection for testing? - javascript

I have piece of code in js like this:
var obj = (function(){
var stateObj = {key:"privateValue"};
return {
getState: function() {
return stateObj.key;
},
publicFn : function(){
//do some operation with stateObj
if(getState() == "test") {
//. . . .
}
}
}
}());
and I tested the code like this:
//test case
sandbox.stub(obj.getState,"test")
assertItShouldGoInsideIfLoop(obj.publicFn())
However on the code review, my team lead said, this is wrong and he asked me to use Dependency injection for these cases.
I really don't know why the above approach is wrong or even why one should use DI.

If I'm interpreting this correctly, the issue here is that your test for the value of calling publicFn depends on the value of stateObj. The entire idea behind dependency injection is that you provide some means to supply these values in your tests in order to decouple the data from the behavior of the function under test.
var obj = (function(){
return {
setStateObj: function(stateObj) {
this.stateObj = stateObj;
},
getState: function() {
return this.stateObj.key;
},
publicFn : function(){
//do some operation with stateObj
if(getState() == "test") {
//. . . .
}
}
}
}());
Now we can use setStateObj to set the state as needed in our tests instead of stubbing the value, which can be dangerous:
obj.setStateObj({ key: 'test' })
assertItExecutesIfStatement(obj.publicFn())
obj.setStateObj({ key: 'blah' })
assertItDoesntExecuteIfStatement(obj.publicFn())
So why is this preferred to stubbing getState? Say we comment the contents of getState:
getState: function() {
// return this.stateObj.key;
}, // returns nothing, but you're stubbing it to return "test" anyway!
Clearly, this function will not work, but with your stub, it will! So you'll have passing tests despite the non-working code!
Here's another example that makes another case for dependency injection. Say I have the following function:
function getDayOfWeek() {
var date = new Date();
var dayNames = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
return dayNames[date.getDay()];
}
This getDayOfWeek function is dependent on date. This would be a nightmare to test since we have no control over date's value. If we rewrite this function with a way to supply a date value (i.e. inject the dependency), we can check test the function for any fixed date easily:
function getDayOfWeek(date) {
date = date || new Date();
var dayNames = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
return dayNames[date.getDay()];
}
assertEqual('Wednesday', getDayOfWeek(new Date(2015, 0, 1)));
assertEqual('Thursday', getDayOfWeek(new Date(2015, 0, 2)));
// and so on...

Related

How to create javascript function constructor with default functionality and persistent properties?

I've got a slightly unusual pattern I'm trying to achieve and have not quite figured it out. My goal is to create a function called debugLog as a flexible console.log replacement, which can be called as follows:
debugLog('thing to log #1', 'thing to log #2', objectToLog1, objectToLog2);
^^ the number of params should be arbitrary, just as is possible with console.log
That is what I'll call the "default" functionality. Now I'd also like to add some additional functionality through property functions.
Examples:
debugLog.setDebugFlag(true); // sets the internal this.debugFlag property to true or false depending on param
I'm trying to do this in Node and what I have so far does not quite let me achieve this pattern:
var debugLog = function () {
this.debugFlag = this.debugFlag || true;
if (this.debugFlag) {
console.log.apply(null, arguments);
} else {
// production mode, nothing to log
}
};
debugLog.prototype.setDebugFlag = function (flagBool) {
this.debugFlag = flagBool;
}
module.exports = new debugLog();
This module would be including in a Node app using the standard require pattern:
var debugLog = require('./debugLog.js');
The question is how can I achieve this pattern of a function object with default functionality, but also extended "property" style functions? Please note I am already aware of the typical pattern where ALL functionality comes from function properties (such as debugLog.log() and debugLog.setDebugFlag()). But that patterns does NOT achieve my key goal of a shorthand for the default functionality that simply involves calling the function directly.
Is it even possible to do?
You could do it this way
var debugLog = (function() {
var debugFlag = true;
function log() {
if (debugFlag) {
console.log.apply(null, arguments);
} else {
// production mode, nothing to log
}
};
log.setDebugFlag = function(flag) {
debugFlag = flag;
}
return log;
})();
module.exports = debugLog;
You could use closure, like so:
// debugLog.js
var debugFlag = true;
function debugLog() {
if (debugFlag) {
console.log.apply(null, arguments);
} else {
// production mode, nothing to log
}
}
debugLog.setDebugFlag = function (newFlag) {
debugFlag = newFlag;
}
module.exports = debugLog;
and use it like this:
// otherFile.js
var debugLog = require('./debugLog');
debugLog('Hey!');
debugLog.setDebugFlag(false);
debugLog('This wont appear!');

cannot modify nodejs module value

I want initialize a module with some default values and change them later if required. To do this I have a module/singleton which contains a _state value. I have exposed a setter method to update that value. However, the setter does not update the _state member variable. The code looks like this:
var StateObject = function () {
var _state = { a: 1 };
return {
state : _state,
setState : function (s) {
_state = s;
}
};
}();
modules.export = StateObject;
and the calling function:
var SO = require('./state-object');
console.log(SO.state.a); // prints 1
SO.setState({a: 2});
console.log(SO.state.a); // still prints 1
Can anyone explain what would cause this and if there is a work around?
The potential pitfall of a solution like this is if some piece of code stores SO.state locally and references that. If that happens and you call setState() some time later, the reference won't be updated in that piece of code. That's just something to be aware of when you replace the whole state and not just individual values in the state.
The problem here has to do with references. When you execute StateObject(), the state variable stores the initial reference to _state. Then when you call setState(), you overwrite _state, but state is still holding on to the previous reference.
You might try something like this:
modules.export = {
state: { a: 1 },
setState: function(v) {
this.state = v;
}
};
Well, the problem is with the reference not being updated, as mscdex mentioned.
In my opinion the main problem is actually your logic: why have a setter if you don't have a getter?
var SO = function () {
var _state = { a: 1 };
return {
getState : function () {
return _state;
},
setState : function (s) {
_state = s;
}
};
}();
console.log(SO.getState().a); // prints 1
SO.setState({a: 2});
console.log(SO.getState().a); // prints 2
This works, as it is also returning the reference to the latest set object. And this has actually nothing to do with node or modules, you can run your example in the browser's JavaScript console.

AngularJS : Asynchronously initialize filter

I'm having trouble trying to initialize a filter with asynchronous data.
The filter is very simple, it needs to translate paths to name, but to do so it needs a correspondance array, which I need to fetch from the server.
I could do things in the filter definition, before returning the function, but the asynchronous aspect prevents that
angular.module('angularApp').
filter('pathToName', function(Service){
// Do some things here
return function(input){
return input+'!'
}
}
Using a promise may be viable but I don't have any clear understanding on how angular loads filters.
This post explains how to achieve such magic with services, but is it possible to do the same for filters?
And if anyone has a better idea on how to translate those paths, I'm all ears.
EDIT:
I tried with the promise approch, but something isn't right, and I fail to see what:
angular.module('angularApp').filter('pathToName', function($q, Service){
var deferred = $q.defer();
var promise = deferred.promise;
Service.getCorresp().then(function(success){
deferred.resolve(success.data);
}, function(error){
deferred.reject();
});
return function(input){
return promise.then(
function(corresp){
if(corresp.hasOwnProperty(input))
return corresp[input];
else
return input;
}
)
};
});
I'm not really familliar with promises, is it the right way to use them?
Here is an example:
app.filter("testf", function($timeout) {
var data = null, // DATA RECEIVED ASYNCHRONOUSLY AND CACHED HERE
serviceInvoked = false;
function realFilter(value) { // REAL FILTER LOGIC
return ...;
}
return function(value) { // FILTER WRAPPER TO COPE WITH ASYNCHRONICITY
if( data === null ) {
if( !serviceInvoked ) {
serviceInvoked = true;
// CALL THE SERVICE THAT FETCHES THE DATA HERE
callService.then(function(result) {
data = result;
});
}
return "-"; // PLACEHOLDER WHILE LOADING, COULD BE EMPTY
}
else return realFilter(value);
}
});
This fiddle is a demonstration using timeouts instead of services.
EDIT: As per the comment of sgimeno, extra care must be taken for not calling the service more than once. See the serviceInvoked changes in the code above and the fiddles. See also forked fiddle with Angular 1.2.1 and a button to change the value and trigger digest cycles: forked fiddle
EDIT 2: As per the comment of Miha Eržen, this solution does no logner work for Angular 1.3. The solution is almost trivial though, using the $stateful filter flag, documented here under "Stateful filters", and the necessary forked fiddle.
Do note that this solution would hurt performance, as the filter is called each digest cycle. The performance degradation could be negligible or not, depending on the specific case.
Let's start with understanding why the original code doesn't work. I've simplified the original question a bit to make it more clear:
angular.module('angularApp').filter('pathToName', function(Service) {
return function(input) {
return Service.getCorresp().then(function(response) {
return response;
});
});
}
Basically, the filter calls an async function that returns the promise, then returns its value. A filter in angular expects you to return a value that can be easily printed, e.g string or number. However, in this case, even though it seems like we're returning the response of getCorresp, we are actually returning a new promise - The return value of any then() or catch() function is a promise.
Angular is trying to convert a promise object to a string via casting, getting nothing sensible in return and displays an empty string.
So what we need to do is, return a temporary string value and change it asynchroniously, like so:
JSFiddle
HTML:
<div ng-app="app" ng-controller="TestCtrl">
<div>{{'WelcomeTo' | translate}}</div>
<div>{{'GoodBye' | translate}}</div>
</div>
Javascript:
app.filter("translate", function($timeout, translationService) {
var isWaiting = false;
var translations = null;
function myFilter(input) {
var translationValue = "Loading...";
if(translations)
{
translationValue = translations[input];
} else {
if(isWaiting === false) {
isWaiting = true;
translationService.getTranslation(input).then(function(translationData) {
console.log("GetTranslation done");
translations = translationData;
isWaiting = false;
});
}
}
return translationValue;
};
return myFilter;
});
Everytime Angular tries to execute the filter, it would check if the translations were fetched already and if they weren't, it would return the "Loading..." value. We also use the isWaiting value to prevent calling the service more than once.
The example above works fine for Angular 1.2, however, among the changes in Angular 1.3, there is a performance improvement that changes the behavior of filters. Previously the filter function was called every digest cycle. Since 1.3, however, it only calls the filter if the value was changed, in our last sample, it would never call the filter again - 'WelcomeTo' would never change.
Luckily the fix is very simple, you'd just need to add to the filter the following:
JSFiddle
myFilter.$stateful = true;
Finally, while dealing with this issue, I had another problem - I needed to use a filter to get async values that could change - Specifically, I needed to fetch translations for a single language, but once the user changed the language, I needed to fetch a new language set. Doing that, proved a bit more tricky, though the concept is the same. This is that code:
JSFiddle
var app = angular.module("app",[]);
debugger;
app.controller("TestCtrl", function($scope, translationService) {
$scope.changeLanguage = function() {
translationService.currentLanguage = "ru";
}
});
app.service("translationService", function($timeout) {
var self = this;
var translations = {"en": {"WelcomeTo": "Welcome!!", "GoodBye": "BYE"},
"ru": {"WelcomeTo": "POZHALUSTA!!", "GoodBye": "DOSVIDANYA"} };
this.currentLanguage = "en";
this.getTranslation = function(placeholder) {
return $timeout(function() {
return translations[self.currentLanguage][placeholder];
}, 2000);
}
})
app.filter("translate", function($timeout, translationService) {
// Sample object: {"en": {"WelcomeTo": {translation: "Welcome!!", processing: false } } }
var translated = {};
var isWaiting = false;
myFilter.$stateful = true;
function myFilter(input) {
if(!translated[translationService.currentLanguage]) {
translated[translationService.currentLanguage] = {}
}
var currentLanguageData = translated[translationService.currentLanguage];
if(!currentLanguageData[input]) {
currentLanguageData[input] = { translation: "", processing: false };
}
var translationData = currentLanguageData[input];
if(!translationData.translation && translationData.processing === false)
{
translationData.processing = true;
translationService.getTranslation(input).then(function(translation) {
console.log("GetTranslation done");
translationData.translation = translation;
translationData.processing = false;
});
}
var translation = translationData.translation;
console.log("Translation for language: '" + translationService.currentLanguage + "'. translation = " + translation);
return translation;
};
return myFilter;
});

How can I detect all dependencies of a function in Node.js?

I'm trying to give a broad picture of my problem. I need to write a program with Node.js that should be able to detect all dependencies a function.
E.g.
function a() {
//do something
b();
};
function b() {
console.log("Hey, This is b");
};
At the example above I need to have an JSON like this:
{
"a": {
dependencies: ["b"],
range: [1, 4]
},
"b": {
dependencies: [],
range: [5, 8]
}
}
In the dependencies property I need to have an array of functions that called inside the function, and by range I mean the line range of function definition.
I need a solution to achieve this goal. Are there any tools or plugins for Node.js?
(I apologise in advance: I usually try and make my answers humorous to ease the reader through them, but I couldn't successfully do so in this case. Consider that a double apology for the length of this answer.)
0. TL;DR (for "normal people") of the problem
This is not an easy problem. Instead of solving it in full, we will limit its scope - we will only solve the portion of the problem we care about. We will do so by parsing the input with a JavaScript parser and going over it with a simple recurive-descent algorithm. Our algorithm will analyse the program's scope and correctly identify function calls.
All the rest is just filling in the blanks! The result is at the bottom of the answer, so I recommend you grab to the first comment if you don't want to read through.
1. Limiting the problem
As Benjamin Gruenbaum's answer says, this is a very, very hard problem because of JavaScript's dynamic nature. However, what if instead of making a solution which'll work for 100% of programs, we instead do it for a subset of programs, if we limit ourselves to handle certain things?
The most important limitation:
No eval. If we include eval, it's a spiral into chaos. This is because eval let's you use arbitrary strings which makes tracking dependencies impossible without checking every possible input. In NodeJS there are no document.writes and setTimeout only accepts a function so we don't have to worry about those. However, we also disallow the vm module.
The following limitations are to ease the process. They may be solvable, but solving them is out of scope for this answer:
No dynamic keys obj[key]() it saddens me to introduce this limitation, but it's definitely solvable for some cases (e.g. key = 'foo' but not key = userInput())
Variables are not shadows, no var self = this. Definitely solvable with a complete scope parser.
No funky expressions, e.g. (a, b)()
And finally, limitations to the implementation in this answer - either because of complexity constraints or time constraints (but they are very solvable):
No hoisting, so function declarations won't bob up in the scope.
No object handling. This sucks, but handling things like foo.bar() or this.foo() would've at least doubled the program complexity. Put in enough time, and it's very doable.
Only function scope is honored. There're ways in JavaScript to define scopes other than functions (the with statement, catch blocks). We don't deal with them.
In this answer, I'll outline (and provide) a proof-of-concept parser.
2. Approaching the problem
Given a program, how can we decipher its function dependencies?
//A. just a global function
globalFunction();
//B. a function within a function
var outer = function () {
function foo () {}
foo();
};
//C. calling a function within itself
var outer = function inner () {
inner();
};
//D. disambiguating between two identically named functions
function foo () {
var foo = function () {};
foo();
}
foo();
In order to understand a program, we need to break its code apart, we need to understand its semantics: we need a parser. I've chosen acorn because I've never used it and heard good praise. I suggest you play with it a bit, see what programs look like in SpiderMonkeys's AST.
Now that we have a magical parser which transforms JavaScript into an AST (an Abstract Syntax Tree), how will we logically handle finding dependencies? We'll need do two things:
Build scopes correctly
Understand which function a function call refers to.
We can see why example D above can be ambiguous: There are two functions called foo, how can we know which one foo() means? That's why we need to implement scoping.
3. Solving the problem
Since the solution is in two parts, let's solve it that way. Beginning from the biggest problem:
3.1. Scoping
So...we have an AST. It has a bunch of nodes. How do we build a scope? Well, we only care about function scope. That eases the process, as we know we only have to deal with functions. But before we talk about how to use scopes, let's define the function which makes scopes.
What does a scope have? It's not a complex being: It has a parent scope (or null if it's the global scope), and it has the items it contains. We need a way to add things to a scope, and get things from one. Let's do that:
var Scope = function (parent) {
var ret = { items : {}, parent : parent, children : [] };
ret.get = function (name) {
if (this.items[name]) {
return this.items[name];
}
if (this.parent) {
return this.parent.get(name);
}
//this is fake, as it also assumes every global reference is legit
return name;
};
ret.add = function (name, val) {
this.items[name] = val;
};
if (parent) {
parent.children.push(ret);
}
return ret;
};
As you may have noticed, I'm cheating in two aspects: First, I'm assigning child scopes. That is to make it easier for us measly humans to see that things are working (otherwise, all scoping would be internal, we'd only see the global scope). Second, I'm assuming the global scope contains all - that is, if foo isn't defined in any scope, then it must be an existing global variable. That may or may not be desirable.
OK, we have a way to represent scopes. Don't crack open the champagne yet, we still have to actually make them! Let's see how a simple function declaration, function f(){} looks like in AST:
{
"type": "Program",
"start": 0,
"end": 14,
"body": [{
"type": "FunctionDeclaration",
"start": 0,
"end": 14,
"id": {
"type": "Identifier",
"start": 9,
"end": 10,
"name": "f"
},
"params": [],
"body": {
"type": "BlockStatement",
"start": 12,
"end": 14,
"body": []
}
}]
}
That's quite a mouthful, but we can brave through it! The juicy part is this:
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "f"
},
"params": [ ... ],
"body": { ... }
}
We have a FunctionDeclaration node with an id property. That id's name is our function's name! Let's assume we have a function walk which takes care of walking over nodes, and currentScope and currentFuncName variables, and we've just arrived at parsing our function declaration node. How do we do it? Code speaks louder than words:
//save our state, so we will return to it after we handled the function
var cachedScope = currentScope,
cachedName = currentFuncName;
//and now we change the state
currentScope = Scope(cachedScope);
currentFuncName = node.id.name;
//create the bindings in the parent and current scopes
//the following lines have a serious bug, we'll get to it later (remember that
// we have to meet Captain Crunchypants)
cachedScope.add(currentFuncName, currentName);
currentScope.add(currentFuncName, currentName);
//continue with the parsing
walk(node.body);
//and restore the state
currentScope = cachedScope;
currentFuncName = cachedName;
But wait, what about function expressions? They behave a bit differently! First and foremost, they don't necessarily have a name, and if they do, it's only visible inside them:
var outer = function inner () {
//outer doesn't exist, inner is visible
};
//outer is visible, inner doesn't exist
Let's make another huge assumption that we've dealt with the variable declaration part - we created the proper binding at the parent scope. Then, the logic above for handling functions changes only slightly:
...
//and now we change the state
currentScope = Scope(cachedScope);
//we signify anonymous functions with <anon>, since a function can never be called that
currentFuncName = node.id ? node.id.name : '<anon>';
...
if (node.id) {
currentScope.add(currentFuncName, currentFuncName);
}
if (node.type === 'FunctionDeclaration') {
cachedScope.add(currentFuncName, currentFuncName);
}
...
And believe it or not, that's more or less the entire scope handling mechanism in the final solution. I expect as you add things like objects it'll get more a bit more complicated, but it not by much.
It's time to meet Captain Crunchpants. The very observant listener will by now have remembered example D. Let's freshen our memory:
function foo () {
function foo () {}
foo();
}
foo();
In parsing that, we need a way to tell the outer foo and the inner foo apart - otherwise, we won't be able to know which of these foo calls, and our dependency finder will be toast. Furthermore, we won't be able to tell them apart in the dependency management - if we just add to the results by function name, we'll get overwriting. In other words, we need an absolute function name.
I chose to represent nesting with separation with a # character. The above, then, has a function foo, with an inner function foo#foo, with a call to foo#foo and a call to foo. Or, for a less confusing example:
var outer = function () {
function inner () {}
inner();
};
outer();
Has a function outer and a function outer#inner. There's a call to outer#inner and a call to outer.
So, let's create this function which takes the previous name, and the current function's name, and mushes them together:
function nameToAbsolute (parent, child) {
//foo + bar => foo#bar
if (parent) {
return parent + '#' + name;
}
return name;
}
And modify our function handling pseudo-code (which is about to come to life! I promise!):
...
currentScope = Scope(cachedScope);
var name = node.id ? node.id.name : '<anon>';
currentFuncName = nameToAbsolute(cachedName, name);
...
if (node.id) {
currentScope.add(name, currentFuncName);
}
if (node.type === 'FunctionDeclaration') {
cachedScope.add(name, currentFuncName);
}
Now we're talking! It's time to move on to actually doing something! Maybe I've lied to you all along and I know nothing, maybe I failed miserably and I continued writing until now because I knew nobody will read this far and I'll get many upvotes because it's a long answer!?
HAH! Keep dreming! There's much more to come! I didn't sit on this for a few days for no reason! (As an interesting social experiment, could anyone upvoting comment, saying something around the lines "Captain Crunchpants was happy to see you"?)
On a more serious note, we should begin making the parser: What holds our state and walks over nodes. Since we'll have two parsers at the end, scope and dependency, we'll make a "master parser" which calls each one when needed:
var parser = {
results : {},
state : {},
parse : function (string) {
this.freshen();
var root = acorn.parse(string);
this.walk(root);
return this.results;
},
freshen : function () {
this.results = {};
this.results.deps = {};
this.state = {};
this.state.scope = this.results.scope = Scope(null);
this.state.name = '';
},
walk : function (node) {
//insert logic here
},
// '' => 'foo'
// 'bar' => 'bar#foo'
nameToAbsolute : function (parent, name) {
return parent ? parent + '#' + name : name;
},
cacheState : function () {
var subject = this.state;
return Object.keys( subject ).reduce(reduce, {});
function reduce (ret, key) {
ret[key] = subject[key];
return ret;
}
},
restoreState : function (st) {
var subject = this.state;
Object.keys(st).forEach(function (key) {
subject[key] = st[key];
});
}
};
That's a bit of cruft, but hopefully it's understandable. We made state into an object, and to make it flexible, cacheState and restoreState are simply cloning/merging.
Now, for our beloved scopeParser:
var scopeParser = {
parseFunction : function (func) {
var startState = parser.cacheState(),
state = parser.state,
name = node.id ? node.id.name : '<anon>';
state.scope = Scope(startState.scope);
state.name = parser.nameToAbsolute(startState.name, name);
if (func.id) {
state.scope.add(name, state.name);
}
if (func.type === 'FunctionDeclaration') {
startState.scope.add(name, state.name);
}
this.addParamsToScope(func);
parser.walk(func.body);
parser.restoreState(startState);
}
};
The casually observant reader will notice that parser.walk is empty. Time to fill 'er up!
walk : function (node) {
var type = node.type;
//yes, this is tight coupling. I will not apologise.
if (type === 'FunctionDeclaration' || type === 'FunctionExpression') {
scopeParser.parseFunction(node)
}
else if (node.type === 'ExpressionStatement') {
this.walk(node.expression);
}
//Program, BlockStatement, ...
else if (node.body && node.body.length) {
node.body.forEach(this.walk, this);
}
else {
console.log(node, 'pass through');
}
//...I'm sorry
}
Again, mostly technicalities - to understand these, you need to play with acorn. We want to make sure we iterate and walk into nodes correctly. Expressions Nodes like (function foo() {}) has an expression property we walk over, BlockStatement Nodes (e.g. the actual body of a function) and Program Nodes have a body array, etc.
Since we have something resembling logic, let's try:
> parser.parse('function foo() {}').scope
{ items: { foo: 'foo' },
parent: null,
children:
[ { items: [Object],
parent: [Circular],
children: [],
get: [Function],
add: [Function] } ],
get: [Function],
add: [Function] }
Neat! Play around with function declarations and expressions, see that they're nested correctly. We did however forget to include variable declaration:
var foo = function () {};
bar = function () {};
A good (and fun!) exercise is adding them yourself. But don't worry - they'll be included in the final parser;
Who'd believe!? We're done with scopes! D-O-N-E! Let's do a cheer!
Oh oh oh...where did you think you're going!? We only solved part of the problem - we still have to find the dependencies! Or did you forget all about it!? Fine, you can go to the toilet. But it better be #1.
3.2. Dependency
Wow, did you even remember we had section numbers? On an unrelated note, when I typed the last sentence, my keyboard made a sound reminiscent of the first note of the Super Mario Theme Song. Which is now stuck in my head.
Ok! So, we have our scopes, we have our function names, it's time to identify function calls! This will not take long. Doing acorn.parse('foo()') gives:
{
"type": "Program",
"body": [{
"type": "ExpressionStatement",
"expression": {
"type": "CallExpression",
"callee": {
"type": "Identifier",
"name": "f"
},
"arguments": []
}
}]
}
So we're looking for a CallExpression. But before we go all walk over it, let's first review our logic. Given this node, what do we do? How do we add a dependency?
This is not a difficult problem, as we already took care of all the scoping. We add to the dependencies of the containing function (parser.state.name) the scope resolution of callExpression.callee.name. Sounds simple!
var deps = parser.results.deps,
scope = parser.state.scope,
context = parser.state.name || '<global>';
if (!deps[context]) {
deps[context] = [];
}
deps[context].push(scope.get(node.callee.name));
There're, once again, a trick with handling the global context. If the current state is nameless, we assume it's the global context and give it a special name <global>.
Now that we have that, let's build our dependencyParser:
var dependencyParser = {
parseCall : function (node) {
...the code above...
}
};
Truly beautiful. We still need to modify parser.walk to include CallExpressions:
walk : function (node) {
...
else if (type === 'CallExpression') {
dependencyParser.parseCall(node);
}
}
And try it out on example D:
> parser.parse('function foo() { var foo = function () {}; foo(); } foo()').deps
{ foo: [ 'foo#foo' ], '<global>': [ 'foo' ] }
4. Mock the problem
HAHA! IN YOUR FACE, PROBLEM! WOOOOOOOOOOO!
You may commence celebrations. Remove your pants, run around in the city, claim you're the town chicken and burn stray garbage cans (Zirak and Affiliates in no way support arson of any kind or indecent exposure. Any action taken by oh, say, any reader is not to be blamed upon Zirak and/or Affiliates).
But seriously now. We solved a very, very limited subset of the problem, and to solve it for a small percentage of real-case scenarios there are a lot of things which have to be done. This is not a discouragement - quite the opposite! I urge you to try and do this. It's fun! (Zirak and Affiliates are in no way responsible for any mental breakdown as a result from trying to to what was just said)
Presented here is the source code of the parser, sans any NodeJS specific stuff (i.e. requiring acorn or exposing the parser):
var parser = {
results : {},
state : {},
verbose : false,
parse : function (string) {
this.freshen();
var root = acorn.parse(string);
this.walk(root);
return this.results;
},
freshen : function () {
this.results = {};
this.results.deps = {};
this.state = {};
this.state.scope = this.results.scope = Scope(null);
this.state.name = '';
},
walk : function (node) {
var type = node.type;
//yes, this is tight coupling. I will not apologise.
if (type === 'FunctionDeclaration' || type === 'FunctionExpression') {
scopeParser.parseFunction(node)
}
else if (type === 'AssignmentExpression') {
scopeParser.parseBareAssignmentExpression(node);
}
else if (type === 'VariableDeclaration') {
scopeParser.parseVarDeclaration(node);
}
else if (type === 'CallExpression') {
dependencyParser.parseCall(node);
}
else if (node.type === 'ExpressionStatement') {
this.walk(node.expression);
}
//Program, BlockStatement, ...
else if (node.body && node.body.length) {
node.body.forEach(this.walk, this);
}
else if (this.verbose) {
console.log(node, 'pass through');
}
//...I'm sorry
},
// '' => 'foo'
// 'bar' => 'bar#foo'
nameToAbsolute : function (parent, name) {
return parent ? parent + '#' + name : name;
},
cacheState : function () {
var subject = this.state;
return Object.keys( subject ).reduce(reduce, {});
function reduce (ret, key) {
ret[key] = subject[key];
return ret;
}
},
restoreState : function (st) {
var subject = this.state;
Object.keys(st).forEach(function (key) {
subject[key] = st[key];
});
}
};
var dependencyParser = {
//foo()
//yes. that's all.
parseCall : function (node) {
if (parser.verbose) {
console.log(node, 'parseCall');
}
var deps = parser.results.deps,
scope = parser.state.scope,
context = parser.state.name || '<global>';
if (!deps[context]) {
deps[context] = [];
}
deps[context].push(scope.get(node.callee.name));
}
};
var scopeParser = {
// We only care about these kinds of tokens:
// (1) Function declarations
// function foo () {}
// (2) Function expressions assigned to variables
// var foo = function () {};
// bar = function () {};
//
// Do note the following property:
// var foo = function bar () {
// `bar` is visible, `foo` is not
// };
// `bar` is not visible, `foo` is
/*
function foo () {}
=>
{
"type": 'FunctionDeclaration',
"id": {
"type": Identifier,
"name": 'foo'
},
"params": [],
"body": { ... }
}
(function () {})
=>
{
"type": "FunctionExpression",
"id": null,
"params": [],
"body": { ... }
}
*/
parseFunction : function (func) {
if (parser.verbose) {
console.log(func, 'parseFunction');
}
var startState = parser.cacheState(),
state = parser.state,
name = this.grabFuncName(func);
state.scope = Scope(startState.scope);
state.name = parser.nameToAbsolute(startState.name, name);
if (func.id) {
state.scope.add(name, state.name);
}
if (func.type === 'FunctionDeclaration') {
startState.scope.add(name, state.name);
}
this.addParamsToScope(func);
parser.walk(func.body);
parser.restoreState(startState);
},
grabFuncName : function (func) {
if (func.id) {
return func.id.name;
}
else if (func.type === 'FunctionExpression') {
return '<anon>';
}
else {
//...this shouldn't happen
throw new Error(
'scope.parseFunction encountered an anomalous function: ' +
'nameless and is not an expression');
}
},
/*
[{
"type": "Identifier",
"name": "a"
}, {
"type": "Identifier",
"name": "b"
}, {
"type": "Identifier",
"name": "c"
}]
*/
addParamsToScope : function (func) {
var scope = parser.state.scope,
fullName = parser.state.name;
func.params.forEach(addParam);
function addParam (param) {
var name = param.name;
scope.add(name, parser.nameToAbsolute(fullName, name));
}
},
parseVarDeclaration : function (tok) {
if (parser.verbose) {
console.log(tok, 'parseVarDeclaration');
}
tok.declarations.forEach(parseDecl, this);
function parseDecl (decl) {
this.parseAssignment(decl.id, decl.init);
}
},
// Lacking a better name, this:
// foo = function () {}
// without a `var`, I call a "bare assignment"
parseBareAssignmentExpression : function (exp) {
if (parser.verbose) {
console.log(exp, 'parseBareAssignmentExpression');
}
this.parseAssignment(exp.left, exp.right);
},
parseAssignment : function (id, value) {
if (parser.verbose) {
console.log(id, value, 'parseAssignment');
}
if (!value || value.type !== 'FunctionExpression') {
return;
}
var name = id.name,
val = parser.nameToAbsolute(parser.state.name, name);
parser.state.scope.add(name, val);
this.parseFunction(value);
}
};
var Scope = function (parent) {
var ret = { items : {}, parent : parent, children : [] };
ret.get = function (name) {
if (this.items[name]) {
return this.items[name];
}
if (this.parent) {
return this.parent.get(name);
}
//this is fake, as it also assumes every global reference is legit
return name;
};
ret.add = function (name, val) {
this.items[name] = val;
};
if (parent) {
parent.children.push(ret);
}
return ret;
};
Now if you'll excuse me, I need a long shower.
No.
Sorry, this is impossible on a pretty theoretical level in a dynamic language with eval. Good IDEs detect basic stuff, but there are some things you simply can't detect very well:
Let's take your simple case:
function a() {
//do something
b();
};
Let's complicate it a bit:
function a() {
//do something
eval("b();")
};
Now we have to detect stuff in strings, let's go one step ahead:
function a() {
//do something
eval("b"+"();");
};
Now we have to detect the result of string concats. Let's do a couple more of those:
function a() {
//do something
var d = ["b"];
eval(d.join("")+"();");
};
Still not happy? Let's encode it:
function a() {
//do something
var d = "YigpOw==";
eval(atob(d));
};
Now, these are some very basic cases, I can complicate them as much as I want. There really is no way around running the code - you'd have to run it on every possible input and check and we all know that that's impractical.
So what can you do?
Pass dependencies as parameters to the function and use inversion of control. Always be explicit about your more complicated dependencies and not implicit. That way you won't need tools to know what your dependencies are :)
You can use statistical profiler log (node --prof yourprogram, v8.log) to calculate 'statistical' call graph. Take a look at log processor source code here and here
Get code of a function as a string: a.toString()
Check with RegEx for possible function calls like possiblefuncname( and possiblefuncname.call( and possiblefuncname.apply(
Check if `typeof possiblefuncname == 'function'
IF 3 is TRUE, Recursively check possiblefuncname for dependencies
Set your dependency.

Change var in object literal function

Hi guys I am writing some code using the object literal pattern, I have function that returns a value:
'currentLocation': function() {
var cL = 0;
return cL;
},
I then need to update the variable 'cL' from another function like this:
teamStatus.currentLocation() = teamStatus.currentLocation() + teamStatus.scrollDistance();
This part is part of another function - however I get an error back stating: invalid assignment left-hand side
I am guessing I can not update the variable in this way, could anyone suggest a better method or point me in the right direction.
Any help would be greatly appreciated.
Going to add more code to highlight what I am trying to do:
'currentLocation': function() {
var cL = 0;
return cL;
},
'increaseTable': function() {
if (teamStatus.currentLocation() <= teamStatus.teamStatusTableHeight() ) {
teamStatus.currentLocation = teamStatus.currentLocation() + teamStatus.scrollDistance();
$("#tableTrackActual").animate({scrollTop: (teamStatus.currentLocation)});
$("#tableMembers").animate({scrollTop: (teamStatus.currentLocation) });
//console.log(teamStatus.currentLocation());
teamStatus.buttonRevealer();
}
}
As you can see increaseTable should update the value of currentLocation - help this sheds more light on what I am trying to achieve.
You're writing teamStatus.currentLocation() =, which calls the function teamStatus.currentLocation and tries to assign to the return value. That isn't valid. You want just teamStatus.currentLocation = — no function call.
The variable inside your function is completely private to that function (and any functions defined within it). If you need to create a number of functions that share a set of private variables, you can do that with a closure. For instance:
var Thing = (function() {
var thingWideData;
function getData() {
return thingWideData;
}
function setData(newData) {
thingWideData = newData;
}
return {
getData: getData,
setData: setData
};
})();
What that does is create a Thing object which has getData and setData functions available for it, which get and set the completely private thingWideData variable contained by the anonymous closure. More about this pattern here and here, although the latter of those is more about private methods than private data.
What your code produces is:
0 = 0 + <some number>
Which variable do you want to update? cL? You are declaring it in the function, you cannot assign a value to it from outside. Depending on the rest of your code, you might be better off with getters and setters:
var object = {
_cL = 0,
get currentLocation() {
return this._cL;
},
set currentLocation(value) {
this._cL = value;
}
}
then you can do:
teamStatus.currentLocation = teamStatus.currentLocation + teamStatus.scrollDistance();
Update:
Regarding IE: If currentLocation should actually be just a number, it might be sufficient to just declare it as property:
var obj = {
currentLocation: 0
}

Categories