Permalink
Browse files

feat($injector): provide API for retrieving function annotations

  • Loading branch information...
mhevery committed May 17, 2012
1 parent 416a783 commit 4361efb03b79e71bf0cea92b94ff377ed718bad4
Showing with 131 additions and 38 deletions.
  1. +115 −27 src/auto/injector.js
  2. +16 −11 test/auto/injectorSpec.js
View
@@ -42,19 +42,32 @@ var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function inferInjectionArgs(fn) {
assertArgFn(fn);
if (!fn.$inject) {
var args = fn.$inject = [];
var fnText = fn.toString().replace(STRIP_COMMENTS, '');
var argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, underscore, name){
args.push(name);
function annotate(fn) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, underscore, name){
$inject.push(name);
});
});
});
fn.$inject = $inject;
}
} else if (isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last], 'fn')
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
}
return fn.$inject;
return $inject;
}
///////////////////////////////////////
@@ -152,6 +165,87 @@ function inferInjectionArgs(fn) {
* @returns {Object} new instance of `Type`.
*/
/**
* @ngdoc method
* @name angular.module.AUTO.$injector#annotate
* @methodOf angular.module.AUTO.$injector
*
* @description
* Returns an array of service names which the function is requesting for injection. This API is used by the injector
* to determine which services need to be injected into the function when the function is invoked. There are three
* ways in which the function can be annotated with the needed dependencies.
*
* # Argument names
*
* The simplest form is to extract the dependencies from the arguments of the function. This is done by converting
* the function into a string using `toString()` method and extracting the argument names.
* <pre>
* // Given
* function MyController($scope, $route) {
* // ...
* }
*
* // Then
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
* </pre>
*
* This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
* are supported.
*
* # The `$injector` property
*
* If a function has an `$inject` property and its value is an array of strings, then the strings represent names of
* services to be injected into the function.
* <pre>
* // Given
* var MyController = function(obfuscatedScope, obfuscatedRoute) {
* // ...
* }
* // Define function dependencies
* MyController.$inject = ['$scope', '$route'];
*
* // Then
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
* </pre>
*
* # The array notation
*
* It is often desirable to inline Injected functions and that's when setting the `$inject` property is very
* inconvenient. In these situations using the array notation to specify the dependencies in a way that survives
* minification is a better choice:
*
* <pre>
* // We wish to write this (not minification / obfuscation safe)
* injector.invoke(function($compile, $rootScope) {
* // ...
* });
*
* // We are forced to write break inlining
* var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
* // ...
* };
* tmpFn.$inject = ['$compile', '$rootScope'];
* injector.invoke(tempFn);
*
* // To better support inline function the inline annotation is supported
* injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
* // ...
* }]);
*
* // Therefore
* expect(injector.annotate(
* ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
* ).toEqual(['$compile', '$rootScope']);
* </pre>
*
* @param {function|Array.<string|Function>} fn Function for which dependent service names need to be retrieved as described
* above.
*
* @returns {Array.<string>} The names of the services which the function requires.
*/
/**
* @ngdoc object
@@ -454,30 +548,23 @@ function createInjector(modulesToLoad) {
function invoke(fn, self, locals){
var args = [],
$inject,
length,
$inject = annotate(fn),
length, i,
key;
if (typeof fn == 'function') {
$inject = inferInjectionArgs(fn);
length = $inject.length;
} else {
if (isArray(fn)) {
$inject = fn;
length = $inject.length - 1;
fn = $inject[length];
}
assertArgFn(fn, 'fn');
}
for(var i = 0; i < length; i++) {
for(i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, path)
);
}
if (!fn.$inject) {
// this means that we must be an array.
fn = fn[length];
}
// Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
switch (self ? -1 : args.length) {
@@ -510,7 +597,8 @@ function createInjector(modulesToLoad) {
return {
invoke: invoke,
instantiate: instantiate,
get: getService
get: getService,
annotate: annotate
};
}
}
View
@@ -123,11 +123,11 @@ describe('injector', function() {
it('should return $inject', function() {
function fn() {}
fn.$inject = ['a'];
expect(inferInjectionArgs(fn)).toBe(fn.$inject);
expect(inferInjectionArgs(function() {})).toEqual([]);
expect(inferInjectionArgs(function () {})).toEqual([]);
expect(inferInjectionArgs(function () {})).toEqual([]);
expect(inferInjectionArgs(function /* */ () {})).toEqual([]);
expect(annotate(fn)).toBe(fn.$inject);
expect(annotate(function() {})).toEqual([]);
expect(annotate(function () {})).toEqual([]);
expect(annotate(function () {})).toEqual([]);
expect(annotate(function /* */ () {})).toEqual([]);
});
@@ -142,43 +142,48 @@ describe('injector', function() {
*/
_c,
/* {some type} */ d) { extraParans();}
expect(inferInjectionArgs($f_n0)).toEqual(['$a', 'b_', '_c', 'd']);
expect(annotate($f_n0)).toEqual(['$a', 'b_', '_c', 'd']);
expect($f_n0.$inject).toEqual(['$a', 'b_', '_c', 'd']);
});
it('should strip leading and trailing underscores from arg name during inference', function() {
function beforeEachFn(_foo_) { /* foo = _foo_ */ };
expect(inferInjectionArgs(beforeEachFn)).toEqual(['foo']);
expect(annotate(beforeEachFn)).toEqual(['foo']);
});
it('should handle no arg functions', function() {
function $f_n0() {}
expect(inferInjectionArgs($f_n0)).toEqual([]);
expect(annotate($f_n0)).toEqual([]);
expect($f_n0.$inject).toEqual([]);
});
it('should handle no arg functions with spaces in the arguments list', function() {
function fn( ) {}
expect(inferInjectionArgs(fn)).toEqual([]);
expect(annotate(fn)).toEqual([]);
expect(fn.$inject).toEqual([]);
});
it('should handle args with both $ and _', function() {
function $f_n0($a_) {}
expect(inferInjectionArgs($f_n0)).toEqual(['$a_']);
expect(annotate($f_n0)).toEqual(['$a_']);
expect($f_n0.$inject).toEqual(['$a_']);
});
it('should throw on non function arg', function() {
expect(function() {
inferInjectionArgs({});
annotate({});
}).toThrow();
});
it('should publish annotate API', function() {
expect(injector.annotate).toBe(annotate);
});
});

0 comments on commit 4361efb

Please sign in to comment.