feat(injector): "strict-DI" mode which disables "automatic" function annotation

This modifies the injector to prevent automatic annotation from occurring for a given injector.

This behaviour can be enabled when bootstrapping the application by using the attribute
"ng-strict-di" on the root element (the element containing "ng-app"), or alternatively by passing
an object with the property "strictDi" set to "true" in angular.bootstrap, when bootstrapping
manually.

JS example:

    angular.module("name", ["dependencies", "otherdeps"])
      .provider("$willBreak", function() {
        this.$get = function($rootScope) {
        };
      })
      .run(["$willBreak", function($willBreak) {
        // This block will never run because the noMagic flag was set to true,
        // and the $willBreak '$get' function does not have an explicit
        // annotation.
      }]);

    angular.bootstrap(document, ["name"], {
      strictDi: true
    });

HTML:

    <html ng-app="name" ng-strict-di>
      <!-- ... -->
    </html>

This will only affect functions with an arity greater than 0, and without an $inject property.

Closes #6719
Closes #6717
Closes #4504
Closes #6069
Closes #3611
This commit is contained in:
Caitlin Potter
2014-03-17 16:36:49 -04:00
parent ab92da43b0
commit f5a04f59cf
8 changed files with 337 additions and 33 deletions

View File

@@ -687,7 +687,7 @@ describe('angular', function() {
var appElement = jqLite('<div ng-app="ABC"></div>')[0];
element.querySelectorAll['[ng-app]'] = [appElement];
angularInit(element, bootstrapSpy);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, ['ABC']);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, ['ABC'], jasmine.any(Object));
});
@@ -695,7 +695,7 @@ describe('angular', function() {
var appElement = jqLite('<div id="ng-app" data-ng-app="ABC"></div>')[0];
jqLite(document.body).append(appElement);
angularInit(element, bootstrapSpy);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, ['ABC']);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, ['ABC'], jasmine.any(Object));
});
@@ -703,7 +703,7 @@ describe('angular', function() {
var appElement = jqLite('<div data-ng-app="ABC"></div>')[0];
element.querySelectorAll['.ng\\:app'] = [appElement];
angularInit(element, bootstrapSpy);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, ['ABC']);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, ['ABC'], jasmine.any(Object));
});
@@ -711,14 +711,14 @@ describe('angular', function() {
var appElement = jqLite('<div x-ng-app="ABC"></div>')[0];
element.querySelectorAll['[ng\\:app]'] = [ appElement ];
angularInit(element, bootstrapSpy);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, ['ABC']);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, ['ABC'], jasmine.any(Object));
});
it('should bootstrap using class name', function() {
var appElement = jqLite('<div class="ng-app: ABC;"></div>')[0];
angularInit(jqLite('<div></div>').append(appElement)[0], bootstrapSpy);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, ['ABC']);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, ['ABC'], jasmine.any(Object));
});
@@ -726,21 +726,21 @@ describe('angular', function() {
var appElement = jqLite('<div x-ng-app></div>')[0];
element.querySelectorAll['[x-ng-app]'] = [ appElement ];
angularInit(element, bootstrapSpy);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, []);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, [], jasmine.any(Object));
});
it('should bootstrap anonymously using class only', function() {
var appElement = jqLite('<div class="ng-app"></div>')[0];
angularInit(jqLite('<div></div>').append(appElement)[0], bootstrapSpy);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, []);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, [], jasmine.any(Object));
});
it('should bootstrap if the annotation is on the root element', function() {
var appElement = jqLite('<div class="ng-app"></div>')[0];
angularInit(appElement, bootstrapSpy);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, []);
expect(bootstrapSpy).toHaveBeenCalledOnceWith(appElement, [], jasmine.any(Object));
});
@@ -778,7 +778,24 @@ describe('angular', function() {
);
dealoc(document);
})
});
it('should bootstrap in strict mode when ng-strict-di attribute is specified', function() {
bootstrapSpy = spyOn(angular, 'bootstrap').andCallThrough();
var appElement = jqLite('<div class="ng-app" ng-strict-di></div>');
angularInit(jqLite('<div></div>').append(appElement[0])[0], bootstrapSpy);
expect(bootstrapSpy).toHaveBeenCalledOnce();
expect(bootstrapSpy.mostRecentCall.args[2].strictDi).toBe(true);
var injector = appElement.injector();
function testFactory($rootScope) {};
expect(function() {
injector.instantiate(testFactory);
}).toThrowMinErr('$injector', 'strictdi');
dealoc(appElement);
});
});

View File

@@ -864,3 +864,69 @@ describe('injector', function() {
});
});
});
describe('strict-di injector', function() {
beforeEach(inject.strictDi(true));
describe('with ngMock', function() {
it('should not throw when calling mock.module() with "magic" annotations', function() {
expect(function() {
module(function($provide, $httpProvider, $compileProvider) {
// Don't throw!
});
}).not.toThrow();
});
it('should not throw when calling mock.inject() with "magic" annotations', function() {
expect(function() {
inject(function($rootScope, $compile, $http) {
// Don't throw!
});
}).not.toThrow();
});
});
it('should throw if magic annotation is used by service', function() {
module(function($provide) {
$provide.service({
'$test': function() { return this; },
'$test2': function($test) { return this; }
});
});
inject(function($injector) {
expect (function() {
$injector.invoke(function($test2) {});
}).toThrowMinErr('$injector', 'strictdi');
});
});
it('should throw if magic annotation is used by provider', function() {
module(function($provide) {
$provide.provider({
'$test': function() { this.$get = function($rootScope) { return $rootScope; }; },
});
});
inject(function($injector) {
expect (function() {
$injector.invoke(['$test', function($test) {}]);
}).toThrowMinErr('$injector', 'strictdi');
});
});
it('should throw if magic annotation is used by factory', function() {
module(function($provide) {
$provide.factory({
'$test': function($rootScope) { return function() {} },
});
});
inject(function($injector) {
expect(function() {
$injector.invoke(['$test', function(test) {}]);
}).toThrowMinErr('$injector', 'strictdi');
});
});
});