ng-annotate
Advanced tools
Comparing version 0.8.0 to 0.9.0
@@ -9,3 +9,5 @@ // ng-annotate-main.js | ||
var alter = require("alter"); | ||
var traverse = require("ast-traverse"); | ||
var traverse = require("ordered-ast-traverse"); | ||
var Heap = require("./heap"); | ||
var ngInjectComments = require("./nginject-comments"); | ||
@@ -17,3 +19,3 @@ var chainedRouteProvider = 1; | ||
function match(node, re) { | ||
function match(node, re, matchPlugins) { | ||
var isMethodCall = ( | ||
@@ -26,5 +28,6 @@ node.type === "CallExpression" && | ||
var matchMethodCalls = (isMethodCall && | ||
(matchRegular(node, re) || matchNgRoute(node) || matchUiRouter(node))); | ||
(matchRegular(node, re) || matchNgRoute(node) || matchUiRouter(node) || matchHttpProvider(node))); | ||
return matchMethodCalls || | ||
(matchPlugins && matchPlugins(node)) || | ||
matchDirectiveReturnObject(node) || | ||
@@ -35,2 +38,4 @@ matchProviderGet(node); | ||
function matchDirectiveReturnObject(node) { | ||
// TODO make these more strict by checking that we're inside an angular module? | ||
// return { .. controller: function($scope, $timeout), ...} | ||
@@ -44,7 +49,12 @@ | ||
function matchProviderGet(node) { | ||
// this.$get = function($scope, $timeout) | ||
// TODO make these more strict by checking that we're inside an angular module? | ||
// (this|self|that).$get = function($scope, $timeout) | ||
// { ... $get: function($scope, $timeout), ...} | ||
return (node.type === "AssignmentExpression" && node.left.type === "MemberExpression" && | ||
node.left.object.type === "ThisExpression" && node.left.property.name === "$get" && node.right) || | ||
var memberExpr; | ||
var self; | ||
return (node.type === "AssignmentExpression" && (memberExpr = node.left).type === "MemberExpression" && | ||
memberExpr.property.name === "$get" && | ||
((self = memberExpr.object).type === "ThisExpression" || (self.type === "Identifier" && is.someof(self.name, ["self", "that"]))) && | ||
node.right) || | ||
(node.type === "ObjectExpression" && matchProp("$get", node.properties)); | ||
@@ -105,3 +115,3 @@ } | ||
// | ||
// $urlRouterProvider.when_otherwise_rule(.., function($scope) {}) | ||
// $urlRouterProvider.when(.., function($scope) {}) | ||
@@ -111,8 +121,13 @@ // we already know that node is a (non-computed) method call | ||
var obj = callee.object; // identifier or expression | ||
var method = callee.property; // identifier | ||
var args = node.arguments; | ||
// special shortcut for $urlRouterProvider.*(.., function($scope) {}) | ||
if ((obj.$chained === chainedUrlRouterProvider || (obj.type === "Identifier" && obj.name === "$urlRouterProvider")) && args.length >= 1) { | ||
// special shortcut for $urlRouterProvider.when(.., function($scope) {}) | ||
if (obj.$chained === chainedUrlRouterProvider || (obj.type === "Identifier" && obj.name === "$urlRouterProvider")) { | ||
node.$chained = chainedUrlRouterProvider; | ||
return last(args); | ||
if (method.name === "when" && args.length >= 1) { | ||
return last(args); | ||
} | ||
return false; | ||
} | ||
@@ -126,3 +141,2 @@ | ||
var method = callee.property; // identifier | ||
if (method.name !== "state") { | ||
@@ -170,2 +184,17 @@ return false; | ||
function matchHttpProvider(node) { | ||
// $httpProvider.interceptors.push(function($scope) {}); | ||
// $httpProvider.responseInterceptors.push(function($scope) {}); | ||
// we already know that node is a (non-computed) method call | ||
var callee = node.callee; | ||
var obj = callee.object; // identifier or expression | ||
var method = callee.property; // identifier | ||
return (method.name === "push" && | ||
obj.type === "MemberExpression" && !obj.computed && | ||
obj.object.name === "$httpProvider" && is.someof(obj.property.name, ["interceptors", "responseInterceptors"]) && | ||
node.arguments.length >= 1 && node.arguments); | ||
} | ||
function matchRegular(node, re) { | ||
@@ -178,3 +207,3 @@ // we already know that node is a (non-computed) method call | ||
var matchAngularModule = (obj.$chained === chainedRegular || isShortDef(obj, re) || isMediumDef(obj, re) || isLongDef(obj)) && | ||
is.someof(method.name, ["provider", "value", "constant", "config", "factory", "directive", "filter", "run", "controller", "service", "decorator", "animation"]); | ||
is.someof(method.name, ["provider", "value", "constant", "bootstrap", "config", "factory", "directive", "filter", "run", "controller", "service", "decorator", "animation"]); | ||
if (!matchAngularModule) { | ||
@@ -185,3 +214,3 @@ return false; | ||
if (is.someof(method.name, ["value", "constant"])) { | ||
if (is.someof(method.name, ["value", "constant", "bootstrap"])) { | ||
return false; // affects matchAngularModule because of chaining | ||
@@ -193,3 +222,3 @@ } | ||
args.length === 1 && args[0] : | ||
args.length === 2 && args[0].type === "Literal" && is.string(args[0].value) && args[1]); | ||
args.length === 2 && ((args[0].type === "Literal" && is.string(args[0].value)) || args[0].type === "Identifier") && args[1]); | ||
} | ||
@@ -204,3 +233,3 @@ | ||
function isMediumDef(node, re) { | ||
if (node.type === "MemberExpression" && is.object(node.object) && is.object(node.property) && is.string(node.object.name) && is.string(node.property.name)) { | ||
if (node.type === "MemberExpression" && is.string(node.object.name) && is.string(node.property.name)) { | ||
return (!re || re.test(node.object.name + "." + node.property.name)); | ||
@@ -293,8 +322,28 @@ } | ||
function replaceRemoveOrInsertArrayForTarget(target, ctx) { | ||
var mode = ctx.mode; | ||
var fragments = ctx.fragments; | ||
var quot = ctx.quot; | ||
if (mode === "rebuild" && isAnnotatedArray(target)) { | ||
replaceArray(target, fragments, quot); | ||
} else if (mode === "remove" && isAnnotatedArray(target)) { | ||
removeArray(target, fragments); | ||
} else if (is.someof(mode, ["add", "rebuild"]) && isFunctionExpressionWithArgs(target)) { | ||
insertArray(target, fragments, quot); | ||
} else { | ||
return false; | ||
} | ||
return true; | ||
} | ||
function isAnnotatedArray(node) { | ||
return node.type === "ArrayExpression" && node.elements.length >= 1 && last(node.elements).type === "FunctionExpression"; | ||
} | ||
function isFunctionWithArgs(node) { | ||
function isFunctionExpressionWithArgs(node) { | ||
return node.type === "FunctionExpression" && node.params.length >= 1; | ||
} | ||
function isFunctionDeclarationWithArgs(node) { | ||
return node.type === "FunctionDeclaration" && node.params.length >= 1; | ||
} | ||
@@ -312,10 +361,75 @@ module.exports = function ngAnnotate(src, options) { | ||
var re = (options.regexp && new RegExp(options.regexp)); | ||
var ast = esprima(src, { | ||
range: true, | ||
var ast; | ||
try { | ||
ast = esprima(src, { | ||
range: true, | ||
comment: true, | ||
}); | ||
} catch(e) { | ||
return { | ||
errors: ["error: couldn't process source due to parse error", e.message], | ||
}; | ||
} | ||
// Fix Program node range (https://code.google.com/p/esprima/issues/detail?id=541) | ||
ast.range[0] = 0; | ||
// append a dummy-node to ast to catch any remaining triggers | ||
ast.body.push({ | ||
type: "DebuggerStatement", | ||
range: [ast.range[1], ast.range[1]], | ||
}); | ||
// detach comments from ast | ||
// [{type: "Block"|"Line", value: str, range: [from,to]}, ..] | ||
var comments = ast.comments; | ||
ast.comments = null; | ||
// all source modifications are built up as operations in the | ||
// fragments array, later sent to alter in one shot | ||
var fragments = []; | ||
traverse(ast, {post: function(node) { | ||
var targets = match(node, re); | ||
// triggers contains functions to trigger when traverse hits the | ||
// first node at (or after) a certain pos | ||
var triggers = new Heap(); | ||
var ctx = { | ||
mode: mode, | ||
quot: quot, | ||
src: src, | ||
comments: comments, | ||
fragments: fragments, | ||
triggers: triggers, | ||
isFunctionExpressionWithArgs: isFunctionExpressionWithArgs, | ||
isFunctionDeclarationWithArgs: isFunctionDeclarationWithArgs, | ||
isAnnotatedArray: isAnnotatedArray, | ||
replaceRemoveOrInsertArrayForTarget: replaceRemoveOrInsertArrayForTarget, | ||
stringify: stringify, | ||
}; | ||
var plugins = options.plugin || []; | ||
function matchPlugins(node, isMethodCall) { | ||
for (var i = 0; i < plugins.length; i++) { | ||
var res = plugins[i].match(node, isMethodCall); | ||
if (res) { | ||
return res; | ||
} | ||
} | ||
return false; | ||
} | ||
var matchPluginsOrNull = (plugins.length === 0 ? null : matchPlugins); | ||
ngInjectComments.init(ctx); | ||
plugins.forEach(function(plugin) { | ||
plugin.init(ctx); | ||
}); | ||
traverse(ast, {pre: function(node) { | ||
var pos = node.range[0]; | ||
while (pos >= triggers.pos) { | ||
var trigger = triggers.getAndRemoveNext(); | ||
trigger.fn.call(null, node, trigger.ctx); | ||
} | ||
}, post: function(node) { | ||
var targets = match(node, re, matchPluginsOrNull); | ||
if (!targets) { | ||
@@ -328,11 +442,5 @@ return; | ||
// TODO add something to know that node has been altered so it won't happen again | ||
for (var i = 0; i < targets.length; i++) { | ||
var target = targets[i]; | ||
if (mode === "rebuild" && isAnnotatedArray(target)) { | ||
replaceArray(target, fragments, quot); | ||
} else if (mode === "remove" && isAnnotatedArray(target)) { | ||
removeArray(target, fragments); | ||
} else if (is.someof(mode, ["add", "rebuild"]) && isFunctionWithArgs(target)) { | ||
insertArray(target, fragments, quot); | ||
} | ||
replaceRemoveOrInsertArrayForTarget(targets[i], ctx); | ||
} | ||
@@ -339,0 +447,0 @@ }}); |
@@ -10,3 +10,3 @@ // ng-annotate.js | ||
var ngAnnotate = require("./ng-annotate-main"); | ||
var version = "0.8.0"; | ||
var version = "0.9.0"; | ||
var optimist = require("optimist") | ||
@@ -30,2 +30,5 @@ .usage("ng-annotate v" + version + "\n\nUsage: ng-annotate OPTIONS file.js") | ||
describe: "detect short form myMod.controller(...) iff myMod matches regexp", | ||
}) | ||
.options("plugin", { | ||
describe: "use plugin with path (experimental)", | ||
}); | ||
@@ -73,8 +76,24 @@ | ||
["add", "remove", "regexp", "single_quotes"].forEach(addOption); | ||
["add", "remove", "regexp", "single_quotes", "plugin"].forEach(addOption); | ||
if (config.plugin) { | ||
if (!Array.isArray(config.plugin)) { | ||
config.plugin = [config.plugin]; | ||
} | ||
config.plugin = config.plugin.map(function(path) { | ||
var absPath = tryor(fs.realpathSync.bind(fs, path), null); | ||
if (!absPath) { | ||
exit(fmt('error: plugin file not found {0}', path)); | ||
} | ||
// the require below may throw an exception on parse-error | ||
// that is fine because it gives the user the line info | ||
return require(absPath); | ||
}); | ||
} | ||
var ret = ngAnnotate(src, config); | ||
if (ret.errors) { | ||
exit(ret.errors.join("\n")); | ||
process.stderr.write(ret.errors.join("\n") + "\n"); | ||
process.exit(1); | ||
} | ||
@@ -81,0 +100,0 @@ |
@@ -25,2 +25,6 @@ "use strict"; | ||
// variable instead of string as first argument | ||
myMod.controller(ctrlName, function($scope, $timeout) {}); | ||
angular.module("MyMod").controller(ctrlName, function($scope, $timeout) {}); | ||
// object property | ||
@@ -76,2 +80,5 @@ var myObj = {}; | ||
}; | ||
self.$get = function($scope) {}; | ||
that.$get = function($scope) {}; | ||
ignore.$get = function($scope) {}; | ||
}); | ||
@@ -128,2 +135,3 @@ myMod.provider("foo", function() { | ||
.constant("foo", "bar") | ||
.bootstrap(element, [], {}) | ||
.factory("foo", function() { | ||
@@ -152,2 +160,9 @@ b; | ||
// $httpProvider | ||
$httpProvider.interceptors.push(function($scope) { a }); | ||
$httpProvider.responseInterceptors.push(function($scope) { a }, function(a, b) { b }, function() { c }); | ||
var interceptor = /*@ngInject*/ function($scope) { a }; | ||
$httpProvider.interceptors.push(interceptor); | ||
// $routeProvider | ||
@@ -231,5 +246,34 @@ $routeProvider.when("path", { | ||
}); | ||
$urlRouterProvider.when("", function($match) { a; }); | ||
$urlRouterProvider.otherwise("", function($location) { a; }); | ||
$urlRouterProvider.rule(function($location) { a; }); | ||
$urlRouterProvider.anythingreally(function($location) { a; }).chained(function($location) { a; }); | ||
$urlRouterProvider.when("/", function($match) { a; }); | ||
$urlRouterProvider.otherwise("", function(a) { a; }); | ||
$urlRouterProvider.rule(function(a) { a; }).anything().when("/", function($location) { a; }); | ||
// explicit annotations | ||
var x = /* @ngInject */ function($scope) { | ||
}; | ||
var obj = {}; | ||
obj.bar = /*@ngInject*/ function($scope) {}; | ||
obj = { | ||
controller: /*@ngInject*/ function($scope) {}, | ||
}; | ||
// @ngInject | ||
function foo($scope) { | ||
} | ||
// @ngInject | ||
// otherstuff | ||
function Foo($scope) { | ||
} | ||
// @ngInject | ||
// has trailing semicolon | ||
var foo = function($scope) { | ||
}; | ||
// @ngInject | ||
// lacks trailing semicolon | ||
var foo = function($scope) { | ||
} |
@@ -25,2 +25,6 @@ "use strict"; | ||
// variable instead of string as first argument | ||
myMod.controller(ctrlName, ['$scope', '$timeout', function($scope, $timeout) {}]); | ||
angular.module("MyMod").controller(ctrlName, ['$scope', '$timeout', function($scope, $timeout) {}]); | ||
// object property | ||
@@ -76,2 +80,5 @@ var myObj = {}; | ||
}]; | ||
self.$get = ['$scope', function($scope) {}]; | ||
that.$get = ['$scope', function($scope) {}]; | ||
ignore.$get = function($scope) {}; | ||
}]); | ||
@@ -128,2 +135,3 @@ myMod.provider("foo", function() { | ||
.constant("foo", "bar") | ||
.bootstrap(element, [], {}) | ||
.factory("foo", function() { | ||
@@ -152,2 +160,9 @@ b; | ||
// $httpProvider | ||
$httpProvider.interceptors.push(['$scope', function($scope) { a }]); | ||
$httpProvider.responseInterceptors.push(['$scope', function($scope) { a }], ['a', 'b', function(a, b) { b }], function() { c }); | ||
var interceptor = /*@ngInject*/ ['$scope', function($scope) { a }]; | ||
$httpProvider.interceptors.push(interceptor); | ||
// $routeProvider | ||
@@ -231,5 +246,38 @@ $routeProvider.when("path", { | ||
}); | ||
$urlRouterProvider.when("", ['$match', function($match) { a; }]); | ||
$urlRouterProvider.otherwise("", ['$location', function($location) { a; }]); | ||
$urlRouterProvider.rule(['$location', function($location) { a; }]); | ||
$urlRouterProvider.anythingreally(['$location', function($location) { a; }]).chained(['$location', function($location) { a; }]); | ||
$urlRouterProvider.when("/", ['$match', function($match) { a; }]); | ||
$urlRouterProvider.otherwise("", function(a) { a; }); | ||
$urlRouterProvider.rule(function(a) { a; }).anything().when("/", ['$location', function($location) { a; }]); | ||
// explicit annotations | ||
var x = /* @ngInject */ ['$scope', function($scope) { | ||
}]; | ||
var obj = {}; | ||
obj.bar = /*@ngInject*/ ['$scope', function($scope) {}]; | ||
obj = { | ||
controller: /*@ngInject*/ ['$scope', function($scope) {}], | ||
}; | ||
// @ngInject | ||
function foo($scope) { | ||
} | ||
foo.$injects = ['$scope']; | ||
// @ngInject | ||
// otherstuff | ||
function Foo($scope) { | ||
} | ||
Foo.$injects = ['$scope']; | ||
// @ngInject | ||
// has trailing semicolon | ||
var foo = function($scope) { | ||
}; | ||
foo.$injects = ['$scope']; | ||
// @ngInject | ||
// lacks trailing semicolon | ||
var foo = function($scope) { | ||
} | ||
foo.$injects = ['$scope']; |
@@ -25,2 +25,6 @@ "use strict"; | ||
// variable instead of string as first argument | ||
myMod.controller(ctrlName, ["$scope", "$timeout", function($scope, $timeout) {}]); | ||
angular.module("MyMod").controller(ctrlName, ["$scope", "$timeout", function($scope, $timeout) {}]); | ||
// object property | ||
@@ -76,2 +80,5 @@ var myObj = {}; | ||
}]; | ||
self.$get = ["$scope", function($scope) {}]; | ||
that.$get = ["$scope", function($scope) {}]; | ||
ignore.$get = function($scope) {}; | ||
}]); | ||
@@ -128,2 +135,3 @@ myMod.provider("foo", function() { | ||
.constant("foo", "bar") | ||
.bootstrap(element, [], {}) | ||
.factory("foo", function() { | ||
@@ -152,2 +160,9 @@ b; | ||
// $httpProvider | ||
$httpProvider.interceptors.push(["$scope", function($scope) { a }]); | ||
$httpProvider.responseInterceptors.push(["$scope", function($scope) { a }], ["a", "b", function(a, b) { b }], function() { c }); | ||
var interceptor = /*@ngInject*/ ["$scope", function($scope) { a }]; | ||
$httpProvider.interceptors.push(interceptor); | ||
// $routeProvider | ||
@@ -231,5 +246,38 @@ $routeProvider.when("path", { | ||
}); | ||
$urlRouterProvider.when("", ["$match", function($match) { a; }]); | ||
$urlRouterProvider.otherwise("", ["$location", function($location) { a; }]); | ||
$urlRouterProvider.rule(["$location", function($location) { a; }]); | ||
$urlRouterProvider.anythingreally(["$location", function($location) { a; }]).chained(["$location", function($location) { a; }]); | ||
$urlRouterProvider.when("/", ["$match", function($match) { a; }]); | ||
$urlRouterProvider.otherwise("", function(a) { a; }); | ||
$urlRouterProvider.rule(function(a) { a; }).anything().when("/", ["$location", function($location) { a; }]); | ||
// explicit annotations | ||
var x = /* @ngInject */ ["$scope", function($scope) { | ||
}]; | ||
var obj = {}; | ||
obj.bar = /*@ngInject*/ ["$scope", function($scope) {}]; | ||
obj = { | ||
controller: /*@ngInject*/ ["$scope", function($scope) {}], | ||
}; | ||
// @ngInject | ||
function foo($scope) { | ||
} | ||
foo.$injects = ["$scope"]; | ||
// @ngInject | ||
// otherstuff | ||
function Foo($scope) { | ||
} | ||
Foo.$injects = ["$scope"]; | ||
// @ngInject | ||
// has trailing semicolon | ||
var foo = function($scope) { | ||
}; | ||
foo.$injects = ["$scope"]; | ||
// @ngInject | ||
// lacks trailing semicolon | ||
var foo = function($scope) { | ||
} | ||
foo.$injects = ["$scope"]; |
@@ -0,1 +1,13 @@ | ||
## v0.9.0 2014-05-13 | ||
* explicit annotations using /* @ngInject */ | ||
* --plugin option to load user plugins (experimental, 0.9.x may change API) | ||
* match $httpProvider.interceptors.push(function($scope) {}) | ||
* match $httpProvider.responseInterceptors.push(function($scope) {}) | ||
* match self and that as aliases to this for this.$get = function($scope){} | ||
* match .controller(name, ..) in addition to .controller("name", ..) | ||
* bugfix ui-router declarations | ||
* bugfix angular.module("MyMod").bootstrap(e, [], {}) disrupting chaining | ||
* even faster (~6% faster annotating angular.js) | ||
* add error array to API return object | ||
## v0.8.0 2014-05-09 | ||
@@ -2,0 +14,0 @@ * ngRoute support: $routeProvider.when("path", { .. }) |
@@ -9,3 +9,5 @@ // ng-annotate-main.js | ||
const alter = require("alter"); | ||
const traverse = require("ast-traverse"); | ||
const traverse = require("ordered-ast-traverse"); | ||
const Heap = require("./heap"); | ||
const ngInjectComments = require("./nginject-comments"); | ||
@@ -17,3 +19,3 @@ const chainedRouteProvider = 1; | ||
function match(node, re) { | ||
function match(node, re, matchPlugins) { | ||
const isMethodCall = ( | ||
@@ -26,5 +28,6 @@ node.type === "CallExpression" && | ||
const matchMethodCalls = (isMethodCall && | ||
(matchRegular(node, re) || matchNgRoute(node) || matchUiRouter(node))); | ||
(matchRegular(node, re) || matchNgRoute(node) || matchUiRouter(node) || matchHttpProvider(node))); | ||
return matchMethodCalls || | ||
(matchPlugins && matchPlugins(node)) || | ||
matchDirectiveReturnObject(node) || | ||
@@ -35,2 +38,4 @@ matchProviderGet(node); | ||
function matchDirectiveReturnObject(node) { | ||
// TODO make these more strict by checking that we're inside an angular module? | ||
// return { .. controller: function($scope, $timeout), ...} | ||
@@ -44,7 +49,12 @@ | ||
function matchProviderGet(node) { | ||
// this.$get = function($scope, $timeout) | ||
// TODO make these more strict by checking that we're inside an angular module? | ||
// (this|self|that).$get = function($scope, $timeout) | ||
// { ... $get: function($scope, $timeout), ...} | ||
return (node.type === "AssignmentExpression" && node.left.type === "MemberExpression" && | ||
node.left.object.type === "ThisExpression" && node.left.property.name === "$get" && node.right) || | ||
let memberExpr; | ||
let self; | ||
return (node.type === "AssignmentExpression" && (memberExpr = node.left).type === "MemberExpression" && | ||
memberExpr.property.name === "$get" && | ||
((self = memberExpr.object).type === "ThisExpression" || (self.type === "Identifier" && is.someof(self.name, ["self", "that"]))) && | ||
node.right) || | ||
(node.type === "ObjectExpression" && matchProp("$get", node.properties)); | ||
@@ -105,3 +115,3 @@ } | ||
// | ||
// $urlRouterProvider.when_otherwise_rule(.., function($scope) {}) | ||
// $urlRouterProvider.when(.., function($scope) {}) | ||
@@ -111,8 +121,13 @@ // we already know that node is a (non-computed) method call | ||
const obj = callee.object; // identifier or expression | ||
const method = callee.property; // identifier | ||
const args = node.arguments; | ||
// special shortcut for $urlRouterProvider.*(.., function($scope) {}) | ||
if ((obj.$chained === chainedUrlRouterProvider || (obj.type === "Identifier" && obj.name === "$urlRouterProvider")) && args.length >= 1) { | ||
// special shortcut for $urlRouterProvider.when(.., function($scope) {}) | ||
if (obj.$chained === chainedUrlRouterProvider || (obj.type === "Identifier" && obj.name === "$urlRouterProvider")) { | ||
node.$chained = chainedUrlRouterProvider; | ||
return last(args); | ||
if (method.name === "when" && args.length >= 1) { | ||
return last(args); | ||
} | ||
return false; | ||
} | ||
@@ -126,3 +141,2 @@ | ||
const method = callee.property; // identifier | ||
if (method.name !== "state") { | ||
@@ -170,2 +184,17 @@ return false; | ||
function matchHttpProvider(node) { | ||
// $httpProvider.interceptors.push(function($scope) {}); | ||
// $httpProvider.responseInterceptors.push(function($scope) {}); | ||
// we already know that node is a (non-computed) method call | ||
const callee = node.callee; | ||
const obj = callee.object; // identifier or expression | ||
const method = callee.property; // identifier | ||
return (method.name === "push" && | ||
obj.type === "MemberExpression" && !obj.computed && | ||
obj.object.name === "$httpProvider" && is.someof(obj.property.name, ["interceptors", "responseInterceptors"]) && | ||
node.arguments.length >= 1 && node.arguments); | ||
} | ||
function matchRegular(node, re) { | ||
@@ -178,3 +207,3 @@ // we already know that node is a (non-computed) method call | ||
const matchAngularModule = (obj.$chained === chainedRegular || isShortDef(obj, re) || isMediumDef(obj, re) || isLongDef(obj)) && | ||
is.someof(method.name, ["provider", "value", "constant", "config", "factory", "directive", "filter", "run", "controller", "service", "decorator", "animation"]); | ||
is.someof(method.name, ["provider", "value", "constant", "bootstrap", "config", "factory", "directive", "filter", "run", "controller", "service", "decorator", "animation"]); | ||
if (!matchAngularModule) { | ||
@@ -185,3 +214,3 @@ return false; | ||
if (is.someof(method.name, ["value", "constant"])) { | ||
if (is.someof(method.name, ["value", "constant", "bootstrap"])) { | ||
return false; // affects matchAngularModule because of chaining | ||
@@ -193,3 +222,3 @@ } | ||
args.length === 1 && args[0] : | ||
args.length === 2 && args[0].type === "Literal" && is.string(args[0].value) && args[1]); | ||
args.length === 2 && ((args[0].type === "Literal" && is.string(args[0].value)) || args[0].type === "Identifier") && args[1]); | ||
} | ||
@@ -204,3 +233,3 @@ | ||
function isMediumDef(node, re) { | ||
if (node.type === "MemberExpression" && is.object(node.object) && is.object(node.property) && is.string(node.object.name) && is.string(node.property.name)) { | ||
if (node.type === "MemberExpression" && is.string(node.object.name) && is.string(node.property.name)) { | ||
return (!re || re.test(node.object.name + "." + node.property.name)); | ||
@@ -293,8 +322,28 @@ } | ||
function replaceRemoveOrInsertArrayForTarget(target, ctx) { | ||
const mode = ctx.mode; | ||
const fragments = ctx.fragments; | ||
const quot = ctx.quot; | ||
if (mode === "rebuild" && isAnnotatedArray(target)) { | ||
replaceArray(target, fragments, quot); | ||
} else if (mode === "remove" && isAnnotatedArray(target)) { | ||
removeArray(target, fragments); | ||
} else if (is.someof(mode, ["add", "rebuild"]) && isFunctionExpressionWithArgs(target)) { | ||
insertArray(target, fragments, quot); | ||
} else { | ||
return false; | ||
} | ||
return true; | ||
} | ||
function isAnnotatedArray(node) { | ||
return node.type === "ArrayExpression" && node.elements.length >= 1 && last(node.elements).type === "FunctionExpression"; | ||
} | ||
function isFunctionWithArgs(node) { | ||
function isFunctionExpressionWithArgs(node) { | ||
return node.type === "FunctionExpression" && node.params.length >= 1; | ||
} | ||
function isFunctionDeclarationWithArgs(node) { | ||
return node.type === "FunctionDeclaration" && node.params.length >= 1; | ||
} | ||
@@ -312,10 +361,75 @@ module.exports = function ngAnnotate(src, options) { | ||
const re = (options.regexp && new RegExp(options.regexp)); | ||
const ast = esprima(src, { | ||
range: true, | ||
let ast; | ||
try { | ||
ast = esprima(src, { | ||
range: true, | ||
comment: true, | ||
}); | ||
} catch(e) { | ||
return { | ||
errors: ["error: couldn't process source due to parse error", e.message], | ||
}; | ||
} | ||
// Fix Program node range (https://code.google.com/p/esprima/issues/detail?id=541) | ||
ast.range[0] = 0; | ||
// append a dummy-node to ast to catch any remaining triggers | ||
ast.body.push({ | ||
type: "DebuggerStatement", | ||
range: [ast.range[1], ast.range[1]], | ||
}); | ||
// detach comments from ast | ||
// [{type: "Block"|"Line", value: str, range: [from,to]}, ..] | ||
const comments = ast.comments; | ||
ast.comments = null; | ||
// all source modifications are built up as operations in the | ||
// fragments array, later sent to alter in one shot | ||
const fragments = []; | ||
traverse(ast, {post: function(node) { | ||
let targets = match(node, re); | ||
// triggers contains functions to trigger when traverse hits the | ||
// first node at (or after) a certain pos | ||
const triggers = new Heap(); | ||
const ctx = { | ||
mode: mode, | ||
quot: quot, | ||
src: src, | ||
comments: comments, | ||
fragments: fragments, | ||
triggers: triggers, | ||
isFunctionExpressionWithArgs: isFunctionExpressionWithArgs, | ||
isFunctionDeclarationWithArgs: isFunctionDeclarationWithArgs, | ||
isAnnotatedArray: isAnnotatedArray, | ||
replaceRemoveOrInsertArrayForTarget: replaceRemoveOrInsertArrayForTarget, | ||
stringify: stringify, | ||
}; | ||
const plugins = options.plugin || []; | ||
function matchPlugins(node, isMethodCall) { | ||
for (let i = 0; i < plugins.length; i++) { | ||
const res = plugins[i].match(node, isMethodCall); | ||
if (res) { | ||
return res; | ||
} | ||
} | ||
return false; | ||
} | ||
const matchPluginsOrNull = (plugins.length === 0 ? null : matchPlugins); | ||
ngInjectComments.init(ctx); | ||
plugins.forEach(function(plugin) { | ||
plugin.init(ctx); | ||
}); | ||
traverse(ast, {pre: function(node) { | ||
const pos = node.range[0]; | ||
while (pos >= triggers.pos) { | ||
const trigger = triggers.getAndRemoveNext(); | ||
trigger.fn.call(null, node, trigger.ctx); | ||
} | ||
}, post: function(node) { | ||
let targets = match(node, re, matchPluginsOrNull); | ||
if (!targets) { | ||
@@ -328,11 +442,5 @@ return; | ||
// TODO add something to know that node has been altered so it won't happen again | ||
for (let i = 0; i < targets.length; i++) { | ||
const target = targets[i]; | ||
if (mode === "rebuild" && isAnnotatedArray(target)) { | ||
replaceArray(target, fragments, quot); | ||
} else if (mode === "remove" && isAnnotatedArray(target)) { | ||
removeArray(target, fragments); | ||
} else if (is.someof(mode, ["add", "rebuild"]) && isFunctionWithArgs(target)) { | ||
insertArray(target, fragments, quot); | ||
} | ||
replaceRemoveOrInsertArrayForTarget(targets[i], ctx); | ||
} | ||
@@ -339,0 +447,0 @@ }}); |
@@ -29,2 +29,5 @@ // ng-annotate.js | ||
describe: "detect short form myMod.controller(...) iff myMod matches regexp", | ||
}) | ||
.options("plugin", { | ||
describe: "use plugin with path (experimental)", | ||
}); | ||
@@ -72,8 +75,24 @@ | ||
["add", "remove", "regexp", "single_quotes"].forEach(addOption); | ||
["add", "remove", "regexp", "single_quotes", "plugin"].forEach(addOption); | ||
if (config.plugin) { | ||
if (!Array.isArray(config.plugin)) { | ||
config.plugin = [config.plugin]; | ||
} | ||
config.plugin = config.plugin.map(function(path) { | ||
const absPath = tryor(fs.realpathSync.bind(fs, path), null); | ||
if (!absPath) { | ||
exit(fmt('error: plugin file not found {0}', path)); | ||
} | ||
// the require below may throw an exception on parse-error | ||
// that is fine because it gives the user the line info | ||
return require(absPath); | ||
}); | ||
} | ||
const ret = ngAnnotate(src, config); | ||
if (ret.errors) { | ||
exit(ret.errors.join("\n")); | ||
process.stderr.write(ret.errors.join("\n") + "\n"); | ||
process.exit(1); | ||
} | ||
@@ -80,0 +99,0 @@ |
{ | ||
"name": "ng-annotate", | ||
"version": "0.8.0", | ||
"version": "0.9.0", | ||
"description": "add, remove and rebuild angularjs dependency injection annotations", | ||
@@ -11,3 +11,2 @@ "main": "build/es5/ng-annotate-main.js", | ||
"dependencies": { | ||
"ast-traverse": "~0.1.1", | ||
"alter": "~0.2.0", | ||
@@ -18,3 +17,5 @@ "simple-fmt": "~0.1.0", | ||
"esprima": "~1.2.0", | ||
"optimist": "~0.6.1" | ||
"optimist": "~0.6.1", | ||
"ordered-ast-traverse": "~0.1.0", | ||
"priorityqueuejs": "~0.2.0" | ||
}, | ||
@@ -21,0 +22,0 @@ "devDependencies": { |
@@ -35,3 +35,6 @@ # ng-annotate | ||
Use the `--plugin` option to load user plugins (experimental, 0.9.x may change API). See | ||
[plugin-example.js](plugin-example.js) for more info. | ||
## Tools support | ||
@@ -79,4 +82,8 @@ * [Grunt](http://gruntjs.com/): [grunt-ng-annotate](https://npmjs.org/package/grunt-ng-annotate) | ||
`angular.module("MyMod").controller(name, ..)` where name is a variable rather than a | ||
string literal is also supported. | ||
ng-annotate understands `this.$get = function($scope) ..` and | ||
`{.., $get: function($scope) ..}` inside a `provider`. | ||
`{.., $get: function($scope) ..}` inside a `provider`. `self` and `that` can be used as | ||
aliases for `this`. | ||
@@ -91,2 +98,5 @@ ng-annotate understands `return {.., controller: function($scope) ..}` inside a | ||
ng-annotate understands `$httpProvider.interceptors.push(function($scope) ..)` and | ||
`$httpProvider.responseInterceptors.push(function($scope) ..)`. | ||
ng-annotate understands [ui-router](https://github.com/angular-ui/ui-router) (`$stateProvider` and | ||
@@ -98,3 +108,40 @@ `$urlRouterProvider`). | ||
## Issues | ||
## Explicit annotations | ||
You can prepend a function expression with `/* @ngInject */` to explicitly state that this | ||
function should get annotated. ng-annotate will leave the comment intact and will thus still | ||
be able to also remove or rewrite such annotations. Use `/* @ngInject */` as an occasional | ||
workaround when ng-annotate doesn't support your code style but feel free to open an issue | ||
also. | ||
var x = /* @ngInject */ function($scope) {}; | ||
obj = {controller: /*@ngInject*/ function($scope) {}}; | ||
obj.bar = /*@ngInject*/ function($scope) {}; | ||
=> | ||
var x = /* @ngInject */ ["$scope", function($scope) {}]; | ||
obj = {controller: /*@ngInject*/ ["$scope", function($scope) {}]}; | ||
obj.bar = /*@ngInject*/ ["$scope", function($scope) {}]; | ||
`/* @ngInject */` can also be prepended to a function statement or a single variable declaration | ||
(where its initializer is a function expression). It will then attach an `$injects` array to | ||
the function. | ||
// @ngInject | ||
function Foo($scope) {} | ||
// @ngInject | ||
var foo = function($scope) {} | ||
=> | ||
// @ngInject | ||
function Foo($scope) {} | ||
Foo.$injects = ["$scope"]; | ||
// @ngInject | ||
var foo = function($scope) {} | ||
foo.$injects = ["$scope"]; | ||
## Issues and compatibility | ||
If ng-annotate does not handle a construct you're using, if there's a bug or if you have a feature | ||
@@ -120,6 +167,9 @@ request then please [file an issue](https://github.com/olov/ng-annotate/issues?state=open). | ||
## Library (API) | ||
ng-annotate can be used as a library. See `ng-annotate.js` for further info about | ||
ng-annotate can be used as a library. See [ng-annotate.js](ng-annotate.js) for further info about | ||
options and return value. | ||
var ngAnnotate = require("ng-annotate"); | ||
var transformedSource = ngAnnotate(src, {add: true}).src; | ||
var somePlugin = require("./some/path/some-plugin"); | ||
var res = ngAnnotate(src, {add: true, plugin: [somePlugin]}) | ||
var errorstringArray = res.errors; | ||
var transformedSource = res.src; |
@@ -25,2 +25,6 @@ "use strict"; | ||
// variable instead of string as first argument | ||
myMod.controller(ctrlName, function($scope, $timeout) {}); | ||
angular.module("MyMod").controller(ctrlName, function($scope, $timeout) {}); | ||
// object property | ||
@@ -76,2 +80,5 @@ var myObj = {}; | ||
}; | ||
self.$get = function($scope) {}; | ||
that.$get = function($scope) {}; | ||
ignore.$get = function($scope) {}; | ||
}); | ||
@@ -128,2 +135,3 @@ myMod.provider("foo", function() { | ||
.constant("foo", "bar") | ||
.bootstrap(element, [], {}) | ||
.factory("foo", function() { | ||
@@ -152,2 +160,9 @@ b; | ||
// $httpProvider | ||
$httpProvider.interceptors.push(function($scope) { a }); | ||
$httpProvider.responseInterceptors.push(function($scope) { a }, function(a, b) { b }, function() { c }); | ||
var interceptor = /*@ngInject*/ function($scope) { a }; | ||
$httpProvider.interceptors.push(interceptor); | ||
// $routeProvider | ||
@@ -231,5 +246,34 @@ $routeProvider.when("path", { | ||
}); | ||
$urlRouterProvider.when("", function($match) { a; }); | ||
$urlRouterProvider.otherwise("", function($location) { a; }); | ||
$urlRouterProvider.rule(function($location) { a; }); | ||
$urlRouterProvider.anythingreally(function($location) { a; }).chained(function($location) { a; }); | ||
$urlRouterProvider.when("/", function($match) { a; }); | ||
$urlRouterProvider.otherwise("", function(a) { a; }); | ||
$urlRouterProvider.rule(function(a) { a; }).anything().when("/", function($location) { a; }); | ||
// explicit annotations | ||
var x = /* @ngInject */ function($scope) { | ||
}; | ||
var obj = {}; | ||
obj.bar = /*@ngInject*/ function($scope) {}; | ||
obj = { | ||
controller: /*@ngInject*/ function($scope) {}, | ||
}; | ||
// @ngInject | ||
function foo($scope) { | ||
} | ||
// @ngInject | ||
// otherstuff | ||
function Foo($scope) { | ||
} | ||
// @ngInject | ||
// has trailing semicolon | ||
var foo = function($scope) { | ||
}; | ||
// @ngInject | ||
// lacks trailing semicolon | ||
var foo = function($scope) { | ||
} |
@@ -25,2 +25,6 @@ "use strict"; | ||
// variable instead of string as first argument | ||
myMod.controller(ctrlName, ['$scope', '$timeout', function($scope, $timeout) {}]); | ||
angular.module("MyMod").controller(ctrlName, ['$scope', '$timeout', function($scope, $timeout) {}]); | ||
// object property | ||
@@ -76,2 +80,5 @@ var myObj = {}; | ||
}]; | ||
self.$get = ['$scope', function($scope) {}]; | ||
that.$get = ['$scope', function($scope) {}]; | ||
ignore.$get = function($scope) {}; | ||
}]); | ||
@@ -128,2 +135,3 @@ myMod.provider("foo", function() { | ||
.constant("foo", "bar") | ||
.bootstrap(element, [], {}) | ||
.factory("foo", function() { | ||
@@ -152,2 +160,9 @@ b; | ||
// $httpProvider | ||
$httpProvider.interceptors.push(['$scope', function($scope) { a }]); | ||
$httpProvider.responseInterceptors.push(['$scope', function($scope) { a }], ['a', 'b', function(a, b) { b }], function() { c }); | ||
var interceptor = /*@ngInject*/ ['$scope', function($scope) { a }]; | ||
$httpProvider.interceptors.push(interceptor); | ||
// $routeProvider | ||
@@ -231,5 +246,38 @@ $routeProvider.when("path", { | ||
}); | ||
$urlRouterProvider.when("", ['$match', function($match) { a; }]); | ||
$urlRouterProvider.otherwise("", ['$location', function($location) { a; }]); | ||
$urlRouterProvider.rule(['$location', function($location) { a; }]); | ||
$urlRouterProvider.anythingreally(['$location', function($location) { a; }]).chained(['$location', function($location) { a; }]); | ||
$urlRouterProvider.when("/", ['$match', function($match) { a; }]); | ||
$urlRouterProvider.otherwise("", function(a) { a; }); | ||
$urlRouterProvider.rule(function(a) { a; }).anything().when("/", ['$location', function($location) { a; }]); | ||
// explicit annotations | ||
var x = /* @ngInject */ ['$scope', function($scope) { | ||
}]; | ||
var obj = {}; | ||
obj.bar = /*@ngInject*/ ['$scope', function($scope) {}]; | ||
obj = { | ||
controller: /*@ngInject*/ ['$scope', function($scope) {}], | ||
}; | ||
// @ngInject | ||
function foo($scope) { | ||
} | ||
foo.$injects = ['$scope']; | ||
// @ngInject | ||
// otherstuff | ||
function Foo($scope) { | ||
} | ||
Foo.$injects = ['$scope']; | ||
// @ngInject | ||
// has trailing semicolon | ||
var foo = function($scope) { | ||
}; | ||
foo.$injects = ['$scope']; | ||
// @ngInject | ||
// lacks trailing semicolon | ||
var foo = function($scope) { | ||
} | ||
foo.$injects = ['$scope']; |
@@ -25,2 +25,6 @@ "use strict"; | ||
// variable instead of string as first argument | ||
myMod.controller(ctrlName, ["$scope", "$timeout", function($scope, $timeout) {}]); | ||
angular.module("MyMod").controller(ctrlName, ["$scope", "$timeout", function($scope, $timeout) {}]); | ||
// object property | ||
@@ -76,2 +80,5 @@ var myObj = {}; | ||
}]; | ||
self.$get = ["$scope", function($scope) {}]; | ||
that.$get = ["$scope", function($scope) {}]; | ||
ignore.$get = function($scope) {}; | ||
}]); | ||
@@ -128,2 +135,3 @@ myMod.provider("foo", function() { | ||
.constant("foo", "bar") | ||
.bootstrap(element, [], {}) | ||
.factory("foo", function() { | ||
@@ -152,2 +160,9 @@ b; | ||
// $httpProvider | ||
$httpProvider.interceptors.push(["$scope", function($scope) { a }]); | ||
$httpProvider.responseInterceptors.push(["$scope", function($scope) { a }], ["a", "b", function(a, b) { b }], function() { c }); | ||
var interceptor = /*@ngInject*/ ["$scope", function($scope) { a }]; | ||
$httpProvider.interceptors.push(interceptor); | ||
// $routeProvider | ||
@@ -231,5 +246,38 @@ $routeProvider.when("path", { | ||
}); | ||
$urlRouterProvider.when("", ["$match", function($match) { a; }]); | ||
$urlRouterProvider.otherwise("", ["$location", function($location) { a; }]); | ||
$urlRouterProvider.rule(["$location", function($location) { a; }]); | ||
$urlRouterProvider.anythingreally(["$location", function($location) { a; }]).chained(["$location", function($location) { a; }]); | ||
$urlRouterProvider.when("/", ["$match", function($match) { a; }]); | ||
$urlRouterProvider.otherwise("", function(a) { a; }); | ||
$urlRouterProvider.rule(function(a) { a; }).anything().when("/", ["$location", function($location) { a; }]); | ||
// explicit annotations | ||
var x = /* @ngInject */ ["$scope", function($scope) { | ||
}]; | ||
var obj = {}; | ||
obj.bar = /*@ngInject*/ ["$scope", function($scope) {}]; | ||
obj = { | ||
controller: /*@ngInject*/ ["$scope", function($scope) {}], | ||
}; | ||
// @ngInject | ||
function foo($scope) { | ||
} | ||
foo.$injects = ["$scope"]; | ||
// @ngInject | ||
// otherstuff | ||
function Foo($scope) { | ||
} | ||
Foo.$injects = ["$scope"]; | ||
// @ngInject | ||
// has trailing semicolon | ||
var foo = function($scope) { | ||
}; | ||
foo.$injects = ["$scope"]; | ||
// @ngInject | ||
// lacks trailing semicolon | ||
var foo = function($scope) { | ||
} | ||
foo.$injects = ["$scope"]; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
167622
51
4760
171
8
7
+ Addedordered-ast-traverse@~0.1.0
+ Addedpriorityqueuejs@~0.2.0
+ Addedordered-ast-traverse@0.1.1(transitive)
+ Addedordered-esprima-props@1.0.0(transitive)
+ Addedpriorityqueuejs@0.2.0(transitive)
- Removedast-traverse@~0.1.1
- Removedast-traverse@0.1.1(transitive)