thingsboard/ui/node_modules/ng-annotate/ng-annotate-main.js
2020-05-19 11:43:42 +03:00

1227 lines
40 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ng-annotate-main.js
// MIT licensed, see LICENSE file
// Copyright (c) 2013-2016 Olov Lassus <olov.lassus@gmail.com>
"use strict";
const fmt = require("simple-fmt");
const is = require("simple-is");
const alter = require("alter");
const traverse = require("ordered-ast-traverse");
let EOL = require("os").EOL;
const assert = require("assert");
const ngInject = require("./nginject");
const generateSourcemap = require("./generate-sourcemap");
const Lut = require("./lut");
const scopeTools = require("./scopetools");
const stringmap = require("stringmap");
const optionalAngularDashboardFramework = require("./optionals/angular-dashboard-framework");
const require_acorn_t0 = Date.now();
const parser = require("acorn").parse;
const require_acorn_t1 = Date.now();
const chainedRouteProvider = 1;
const chainedUrlRouterProvider = 2;
const chainedStateProvider = 3;
const chainedRegular = 4;
function match(node, ctx, matchPlugins) {
const isMethodCall = (
node.type === "CallExpression" &&
node.callee.type === "MemberExpression" &&
node.callee.computed === false
);
// matchInjectorInvoke must happen before matchRegular
// to prevent false positive ($injector.invoke() outside module)
// matchProvide must happen before matchRegular
// to prevent regular from matching it as a short-form
const matchMethodCalls = (isMethodCall &&
(matchInjectorInvoke(node) || matchProvide(node, ctx) || matchRegular(node, ctx) || matchNgRoute(node) || matchMaterialShowModalOpen(node) || matchNgUi(node) || matchHttpProvider(node) || matchControllerProvider(node)));
return matchMethodCalls ||
(matchPlugins && matchPlugins(node)) ||
matchDirectiveReturnObject(node) ||
matchProviderGet(node);
}
function matchMaterialShowModalOpen(node) {
// $mdDialog.show({.. controller: fn, resolve: {f: function($scope) {}, ..}});
// $mdToast.show({.. controller: fn, resolve: {f: function($scope) {}, ..}});
// $mdBottomSheet.show({.. controller: fn, resolve: {f: function($scope) {}, ..}});
// $modal.open({.. controller: fn, resolve: {f: 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
const args = node.arguments;
if (obj.type === "Identifier" &&
((is.someof(obj.name, ["$modal", "$uibModal"]) && method.name === "open") || (is.someof(obj.name, ["$mdDialog", "$mdToast", "$mdBottomSheet"]) && method.name === "show")) &&
args.length === 1 && args[0].type === "ObjectExpression") {
const props = args[0].properties;
const res = [matchProp("controller", props)];
res.push.apply(res, matchResolve(props));
return res.filter(Boolean);
}
return false;
}
function matchDirectiveReturnObject(node) {
// only matches inside directives
// return { .. controller: function($scope, $timeout), ...}
return limit("directive", node.type === "ReturnStatement" &&
node.argument && node.argument.type === "ObjectExpression" &&
matchProp("controller", node.argument.properties));
}
function limit(name, node) {
if (node && !node.$limitToMethodName) {
node.$limitToMethodName = name;
}
return node;
}
function matchProviderGet(node) {
// only matches inside providers
// (this|self|that).$get = function($scope, $timeout)
// { ... $get: function($scope, $timeout), ...}
let memberExpr;
let self;
return limit("provider", (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)));
}
function matchNgRoute(node) {
// $routeProvider.when("path", {
// ...
// controller: function($scope) {},
// resolve: {f: function($scope) {}, ..}
// })
// we already know that node is a (non-computed) method call
const callee = node.callee;
const obj = callee.object; // identifier or expression
if (!(obj.$chained === chainedRouteProvider || (obj.type === "Identifier" && obj.name === "$routeProvider"))) {
return false;
}
node.$chained = chainedRouteProvider;
const method = callee.property; // identifier
if (method.name !== "when") {
return false;
}
const args = node.arguments;
if (args.length !== 2) {
return false;
}
const configArg = last(args)
if (configArg.type !== "ObjectExpression") {
return false;
}
const props = configArg.properties;
const res = [
matchProp("controller", props)
];
// {resolve: ..}
res.push.apply(res, matchResolve(props));
const filteredRes = res.filter(Boolean);
return (filteredRes.length === 0 ? false : filteredRes);
}
function matchNgUi(node) {
// $stateProvider.state("myState", {
// ...
// controller: function($scope)
// controllerProvider: function($scope)
// templateProvider: function($scope)
// onEnter: function($scope)
// onExit: function($scope)
// });
// $stateProvider.state("myState", {... resolve: {f: function($scope) {}, ..} ..})
// $stateProvider.state("myState", {... params: {params: {simple: function($scope) {}, inValue: { value: function($scope) {} }} ..})
// $stateProvider.state("myState", {... views: {... somename: {... controller: fn, controllerProvider: fn, templateProvider: fn, resolve: {f: fn}}}})
//
// stateHelperProvider.setNestedState({ sameasregularstate, children: [sameasregularstate, ..]})
// stateHelperProvider.setNestedState({ sameasregularstate, children: [sameasregularstate, ..]}, true)
//
// $urlRouterProvider.when(.., function($scope) {})
//
// $modal.open see matchMaterialShowModalOpen
// 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
const args = node.arguments;
// shortcut for $urlRouterProvider.when(.., function($scope) {})
if (obj.$chained === chainedUrlRouterProvider || (obj.type === "Identifier" && obj.name === "$urlRouterProvider")) {
node.$chained = chainedUrlRouterProvider;
if (method.name === "when" && args.length >= 1) {
return last(args);
}
return false;
}
// everything below is for $stateProvider and stateHelperProvider alone
if (!(obj.$chained === chainedStateProvider || (obj.type === "Identifier" && is.someof(obj.name, ["$stateProvider", "stateHelperProvider"])))) {
return false;
}
node.$chained = chainedStateProvider;
if (is.noneof(method.name, ["state", "setNestedState"])) {
return false;
}
// $stateProvider.state({ ... }) and $stateProvider.state("name", { ... })
// stateHelperProvider.setNestedState({ .. }) and stateHelperProvider.setNestedState({ .. }, true)
if (!(args.length >= 1 && args.length <= 2)) {
return false;
}
const configArg = (method.name === "state" ? last(args) : args[0]);
const res = [];
recursiveMatch(configArg);
const filteredRes = res.filter(Boolean);
return (filteredRes.length === 0 ? false : filteredRes);
function recursiveMatch(objectExpressionNode) {
if (!objectExpressionNode || objectExpressionNode.type !== "ObjectExpression") {
return false;
}
const properties = objectExpressionNode.properties;
matchStateProps(properties, res);
const childrenArrayExpression = matchProp("children", properties);
const children = childrenArrayExpression && childrenArrayExpression.elements;
if (!children) {
return;
}
children.forEach(recursiveMatch);
}
function matchStateProps(props, res) {
const simple = [
matchProp("controller", props),
matchProp("controllerProvider", props),
matchProp("templateProvider", props),
matchProp("onEnter", props),
matchProp("onExit", props),
];
res.push.apply(res, simple);
// {resolve: ..}
res.push.apply(res, matchResolve(props));
// {params: {simple: function($scope) {}, inValue: { value: function($scope) {} }}
const a = matchProp("params", props);
if (a && a.type === "ObjectExpression") {
a.properties.forEach(function(prop) {
if (prop.value.type === "ObjectExpression") {
res.push(matchProp("value", prop.value.properties));
} else {
res.push(prop.value);
}
});
}
// {view: ...}
const viewObject = matchProp("views", props);
if (viewObject && viewObject.type === "ObjectExpression") {
viewObject.properties.forEach(function(prop) {
if (prop.value.type === "ObjectExpression") {
res.push(matchProp("controller", prop.value.properties));
res.push(matchProp("controllerProvider", prop.value.properties));
res.push(matchProp("templateProvider", prop.value.properties));
res.push.apply(res, matchResolve(prop.value.properties));
}
});
}
}
}
function matchInjectorInvoke(node) {
// $injector.invoke(function($compile) { ... });
// 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 === "invoke" &&
obj.type === "Identifier" && obj.name === "$injector" &&
node.arguments.length >= 1 && node.arguments;
}
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 matchControllerProvider(node) {
// $controllerProvider.register("foo", 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
const args = node.arguments;
const target = obj.type === "Identifier" && obj.name === "$controllerProvider" &&
method.name === "register" && args.length === 2 && args[1];
if (target) {
target.$methodName = method.name;
}
return target;
}
function matchProvide(node, ctx) {
// $provide.decorator("foo", function($scope) {});
// $provide.service("foo", function($scope) {});
// $provide.factory("foo", function($scope) {});
// $provide.provider("foo", 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
const args = node.arguments;
const target = obj.type === "Identifier" && obj.name === "$provide" &&
is.someof(method.name, ["decorator", "service", "factory", "provider"]) &&
args.length === 2 && args[1];
if (target) {
target.$methodName = method.name;
if (ctx.rename) {
// for eventual rename purposes
return args;
}
}
return target;
}
function matchRegular(node, ctx) {
// 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
// short-cut implicit config special case:
// angular.module("MyMod", function(a) {})
if (obj.name === "angular" && method.name === "module") {
const args = node.arguments;
if (args.length >= 2) {
node.$chained = chainedRegular;
return last(args);
}
}
// hardcoded exception: foo.decorator is generally considered a short-form
// declaration but $stateProvider.decorator is not. see https://github.com/olov/ng-annotate/issues/82
if (obj.name === "$stateProvider" && method.name === "decorator") {
return false;
}
const matchAngularModule = (obj.$chained === chainedRegular || isReDef(obj, ctx) || isLongDef(obj)) &&
is.someof(method.name, ["provider", "value", "constant", "bootstrap", "config", "factory", "directive", "filter", "run", "controller", "service", "animation", "invoke", "store", "decorator", "component"]);
if (!matchAngularModule) {
return false;
}
node.$chained = chainedRegular;
if (is.someof(method.name, ["value", "constant", "bootstrap"])) {
return false; // affects matchAngularModule because of chaining
}
const args = node.arguments;
let target = (is.someof(method.name, ["config", "run"]) ?
args.length === 1 && args[0] :
args.length === 2 && args[0].type === "Literal" && is.string(args[0].value) && args[1]);
if (method.name === "component") {
const controllerProp = (target && target.type === "ObjectExpression" && matchProp("controller", target.properties));
if (!controllerProp) {
return false;
}
target = controllerProp;
}
if (target) {
target.$methodName = method.name;
}
if (ctx.rename && args.length === 2 && target) {
// for eventual rename purposes
const somethingNameLiteral = args[0];
return [somethingNameLiteral, target];
}
return target;
}
// matches with default regexp
// *.controller("MyCtrl", function($scope, $timeout) {});
// *.*.controller("MyCtrl", function($scope, $timeout) {});
// matches with --regexp "^require(.*)$"
// require("app-module").controller("MyCtrl", function($scope) {});
function isReDef(node, ctx) {
return ctx.re.test(ctx.srcForRange(node.range));
}
// Long form: angular.module(*).controller("MyCtrl", function($scope, $timeout) {});
function isLongDef(node) {
return node.callee &&
node.callee.object && node.callee.object.name === "angular" &&
node.callee.property && node.callee.property.name === "module";
}
function last(arr) {
return arr[arr.length - 1];
}
function matchProp(name, props) {
for (let i = 0; i < props.length; i++) {
const prop = props[i];
if ((prop.key.type === "Identifier" && prop.key.name === name) ||
(prop.key.type === "Literal" && prop.key.value === name)) {
return prop.value; // FunctionExpression or ArrayExpression
}
}
return null;
}
function matchResolve(props) {
const resolveObject = matchProp("resolve", props);
if (resolveObject && resolveObject.type === "ObjectExpression") {
return resolveObject.properties.map(function(prop) {
return prop.value;
});
}
return [];
};
function renamedString(ctx, originalString) {
if (ctx.rename) {
return ctx.rename.get(originalString) || originalString;
}
return originalString;
}
function stringify(ctx, arr, quot) {
return "[" + arr.map(function(arg) {
return quot + renamedString(ctx, arg.name) + quot;
}).join(", ") + "]";
}
function parseExpressionOfType(str, type) {
const node = parser(str).body[0].expression;
assert(node.type === type);
return node;
}
// stand-in for not having a jsshaper-style ref's
function replaceNodeWith(node, newNode) {
let done = false;
const parent = node.$parent;
const keys = Object.keys(parent);
keys.forEach(function(key) {
if (parent[key] === node) {
parent[key] = newNode;
done = true;
}
});
if (done) {
return;
}
// second pass, now check arrays
keys.forEach(function(key) {
if (Array.isArray(parent[key])) {
const arr = parent[key];
for (let i = 0; i < arr.length; i++) {
if (arr[i] === node) {
arr[i] = newNode;
done = true;
}
}
}
});
assert(done);
}
function insertArray(ctx, functionExpression, fragments, quot) {
const args = stringify(ctx, functionExpression.params, quot);
fragments.push({
start: functionExpression.range[0],
end: functionExpression.range[0],
str: args.slice(0, -1) + ", ",
loc: {
start: functionExpression.loc.start,
end: functionExpression.loc.start
}
});
fragments.push({
start: functionExpression.range[1],
end: functionExpression.range[1],
str: "]",
loc: {
start: functionExpression.loc.end,
end: functionExpression.loc.end
}
});
}
function replaceArray(ctx, array, fragments, quot) {
const functionExpression = last(array.elements);
if (functionExpression.params.length === 0) {
return removeArray(array, fragments);
}
const args = stringify(ctx, functionExpression.params, quot);
fragments.push({
start: array.range[0],
end: functionExpression.range[0],
str: args.slice(0, -1) + ", ",
loc: {
start: array.loc.start,
end: functionExpression.loc.start
}
});
}
function removeArray(array, fragments) {
const functionExpression = last(array.elements);
fragments.push({
start: array.range[0],
end: functionExpression.range[0],
str: "",
loc: {
start: array.loc.start,
end: functionExpression.loc.start
}
});
fragments.push({
start: functionExpression.range[1],
end: array.range[1],
str: "",
loc: {
start: functionExpression.loc.end,
end: array.loc.end
}
});
}
function renameProviderDeclarationSite(ctx, literalNode, fragments) {
fragments.push({
start: literalNode.range[0] + 1,
end: literalNode.range[1] - 1,
str: renamedString(ctx, literalNode.value),
loc: {
start: {
line: literalNode.loc.start.line,
column: literalNode.loc.start.column + 1
}, end: {
line: literalNode.loc.end.line,
column: literalNode.loc.end.column - 1
}
}
});
}
function judgeSuspects(ctx) {
const mode = ctx.mode;
const fragments = ctx.fragments;
const quot = ctx.quot;
const blocked = ctx.blocked;
const suspects = makeUnique(ctx.suspects, 1);
for (let n = 0; n < 42; n++) {
// could be while(true), above is just a safety-net
// in practice it will loop just a couple of times
propagateModuleContextAndMethodName(suspects);
if (!setChainedAndMethodNameThroughIifesAndReferences(suspects)) {
break;
}
}
// create final suspects by jumping, following, uniq'ing, blocking
const finalSuspects = makeUnique(suspects.map(function(target) {
const jumped = jumpOverIife(target);
const jumpedAndFollowed = followReference(jumped) || jumped;
if (target.$limitToMethodName && target.$limitToMethodName !== "*never*" && findOuterMethodName(target) !== target.$limitToMethodName) {
return null;
}
if (blocked.indexOf(jumpedAndFollowed) >= 0) {
return null;
}
return jumpedAndFollowed;
}).filter(Boolean), 2);
finalSuspects.forEach(function(target) {
if (target.$chained !== chainedRegular) {
return;
}
if (mode === "rebuild" && isAnnotatedArray(target)) {
replaceArray(ctx, target, fragments, quot);
} else if (mode === "remove" && isAnnotatedArray(target)) {
removeArray(target, fragments);
} else if (is.someof(mode, ["add", "rebuild"]) && isFunctionExpressionWithArgs(target)) {
insertArray(ctx, target, fragments, quot);
} else if (isGenericProviderName(target)) {
renameProviderDeclarationSite(ctx, target, fragments);
} else {
// if it's not array or function-expression, then it's a candidate for foo.$inject = [..]
judgeInjectArraySuspect(target, ctx);
}
});
function propagateModuleContextAndMethodName(suspects) {
suspects.forEach(function(target) {
if (target.$chained !== chainedRegular && isInsideModuleContext(target)) {
target.$chained = chainedRegular;
}
if (!target.$methodName) {
const methodName = findOuterMethodName(target);
if (methodName) {
target.$methodName = methodName;
}
}
});
}
function findOuterMethodName(node) {
for (; node && !node.$methodName; node = node.$parent) {
}
return node ? node.$methodName : null;
}
function setChainedAndMethodNameThroughIifesAndReferences(suspects) {
let modified = false;
suspects.forEach(function(target) {
const jumped = jumpOverIife(target);
if (jumped !== target) { // we did skip an IIFE
if (target.$chained === chainedRegular && jumped.$chained !== chainedRegular) {
modified = true;
jumped.$chained = chainedRegular;
}
if (target.$methodName && !jumped.$methodName) {
modified = true;
jumped.$methodName = target.$methodName;
}
}
const jumpedAndFollowed = followReference(jumped) || jumped;
if (jumpedAndFollowed !== jumped) { // we did follow a reference
if (jumped.$chained === chainedRegular && jumpedAndFollowed.$chained !== chainedRegular) {
modified = true;
jumpedAndFollowed.$chained = chainedRegular;
}
if (jumped.$methodName && !jumpedAndFollowed.$methodName) {
modified = true;
jumpedAndFollowed.$methodName = jumped.$methodName;
}
}
});
return modified;
}
function isInsideModuleContext(node) {
let $parent = node.$parent;
for (; $parent && $parent.$chained !== chainedRegular; $parent = $parent.$parent) {
}
return Boolean($parent);
}
function makeUnique(suspects, val) {
return suspects.filter(function(target) {
if (target.$seen === val) {
return false;
}
target.$seen = val;
return true;
});
}
}
function followReference(node) {
if (!scopeTools.isReference(node)) {
return null;
}
const scope = node.$scope.lookup(node.name);
if (!scope) {
return null;
}
const parent = scope.getNode(node.name).$parent;
const kind = scope.getKind(node.name);
if (!parent) {
return null;
}
const ptype = parent.type;
if (is.someof(kind, ["const", "let", "var"])) {
assert(ptype === "VariableDeclarator");
// {type: "VariableDeclarator", id: {type: "Identifier", name: "foo"}, init: ..}
return parent;
} else if (kind === "fun") {
assert(ptype === "FunctionDeclaration" || ptype === "FunctionExpression")
// FunctionDeclaration is the common case, i.e.
// function foo(a, b) {}
// FunctionExpression is only applicable for cases similar to
// var f = function asdf(a,b) { mymod.controller("asdf", asdf) };
return parent;
}
// other kinds should not be handled ("param", "caught")
return null;
}
// O(srclength) so should only be used for debugging purposes, else replace with lut
function posToLine(pos, src) {
if (pos >= src.length) {
pos = src.length - 1;
}
if (pos <= -1) {
return -1;
}
let line = 1;
for (let i = 0; i < pos; i++) {
if (src[i] === "\n") {
++line;
}
}
return line;
}
function firstNonPrologueStatement(body) {
for (let i = 0; i < body.length; i++) {
if (body[i].type !== "ExpressionStatement") {
return body[i];
}
const expr = body[i].expression;
const isStringLiteral = (expr.type === "Literal" && typeof expr.value === "string");
if (!isStringLiteral) {
return body[i];
}
}
return null;
}
function judgeInjectArraySuspect(node, ctx) {
if (node.type === "VariableDeclaration") {
// suspect can only be a VariableDeclaration (statement) in case of
// explicitly marked via /*@ngInject*/, not via references because
// references follow to VariableDeclarator (child)
// /*@ngInject*/ var foo = function($scope) {} and
if (node.declarations.length !== 1) {
// more than one declarator => exit
return;
}
// one declarator => jump over declaration into declarator
// rest of code will treat it as any (referenced) declarator
node = node.declarations[0];
}
// onode is a top-level node (inside function block), later verified
// node is inner match, descent in multiple steps
let onode = null;
let declaratorName = null;
if (node.type === "VariableDeclarator") {
onode = node.$parent;
declaratorName = node.id.name;
node = node.init; // var foo = ___;
} else {
onode = node;
}
// suspect must be inside of a block or at the top-level (i.e. inside of node.$parent.body[])
if (!node || !onode.$parent || is.noneof(onode.$parent.type, ["Program", "BlockStatement"])) {
return;
}
const insertPos = {
pos: onode.range[1],
loc: onode.loc.end
};
const isSemicolonTerminated = (ctx.src[insertPos.pos - 1] === ";");
node = jumpOverIife(node);
if (ctx.isFunctionExpressionWithArgs(node)) {
// var x = 1, y = function(a,b) {}, z;
assert(declaratorName);
addRemoveInjectArray(
node.params,
isSemicolonTerminated ? insertPos : {
pos: node.range[1],
loc: node.loc.end
},
declaratorName);
} else if (ctx.isFunctionDeclarationWithArgs(node)) {
// /*@ngInject*/ function foo($scope) {}
addRemoveInjectArray(
node.params,
insertPos,
node.id.name);
} else if (node.type === "ExpressionStatement" && node.expression.type === "AssignmentExpression" &&
ctx.isFunctionExpressionWithArgs(node.expression.right)) {
// /*@ngInject*/ foo.bar[0] = function($scope) {}
const name = ctx.srcForRange(node.expression.left.range);
addRemoveInjectArray(
node.expression.right.params,
isSemicolonTerminated ? insertPos : {
pos: node.expression.right.range[1],
loc: node.expression.right.loc.end
},
name);
} else if (node = followReference(node)) {
// node was a reference and followed node now is either a
// FunctionDeclaration or a VariableDeclarator
// => recurse
judgeInjectArraySuspect(node, ctx);
}
function getIndent(pos) {
const src = ctx.src;
const lineStart = src.lastIndexOf("\n", pos - 1) + 1;
let i = lineStart;
for (; src[i] === " " || src[i] === "\t"; i++) {
}
return src.slice(lineStart, i);
}
function addRemoveInjectArray(params, posAfterFunctionDeclaration, name) {
// if an existing something.$inject = [..] exists then is will always be recycled when rebuilding
const indent = getIndent(posAfterFunctionDeclaration.pos);
let foundSuspectInBody = false;
let existingExpressionStatementWithArray = null;
let nodeAfterExtends = null;
onode.$parent.body.forEach(function(bnode, idx) {
if (bnode === onode) {
foundSuspectInBody = true;
}
if (hasInjectArray(bnode)) {
if (existingExpressionStatementWithArray) {
throw fmt("conflicting inject arrays at line {0} and {1}",
posToLine(existingExpressionStatementWithArray.range[0], ctx.src),
posToLine(bnode.range[0], ctx.src));
}
existingExpressionStatementWithArray = bnode;
}
let e;
if (!nodeAfterExtends && !foundSuspectInBody && bnode.type === "ExpressionStatement" && (e = bnode.expression).type === "CallExpression" && e.callee.type === "Identifier" && e.callee.name === "__extends") {
const nextStatement = onode.$parent.body[idx + 1];
if (nextStatement) {
nodeAfterExtends = nextStatement;
}
}
});
assert(foundSuspectInBody);
if (onode.type === "FunctionDeclaration") {
if (!nodeAfterExtends) {
nodeAfterExtends = firstNonPrologueStatement(onode.$parent.body);
}
if (nodeAfterExtends && !existingExpressionStatementWithArray) {
posAfterFunctionDeclaration = skipPrevNewline(nodeAfterExtends.range[0], nodeAfterExtends.loc.start);
}
}
function hasInjectArray(node) {
let lvalue;
let assignment;
return (node && node.type === "ExpressionStatement" && (assignment = node.expression).type === "AssignmentExpression" &&
assignment.operator === "=" &&
(lvalue = assignment.left).type === "MemberExpression" &&
((lvalue.computed === false && ctx.srcForRange(lvalue.object.range) === name && lvalue.property.name === "$inject") ||
(lvalue.computed === true && ctx.srcForRange(lvalue.object.range) === name && lvalue.property.type === "Literal" && lvalue.property.value === "$inject")));
}
function skipPrevNewline(pos, loc) {
let prevLF = ctx.src.lastIndexOf("\n", pos);
if (prevLF === -1) {
return { pos: pos, loc: loc };
}
if (prevLF >= 1 && ctx.src[prevLF - 1] === "\r") {
--prevLF;
}
if (/\S/g.test(ctx.src.slice(prevLF, pos - 1))) { // any non-whitespace chars between prev newline and pos?
return { pos: pos, loc: loc };
}
return {
pos: prevLF,
loc: {
line: loc.line - 1,
column: prevLF - ctx.src.lastIndexOf("\n", prevLF) - 1,
}
};
}
if (ctx.mode === "rebuild" && existingExpressionStatementWithArray) {
const strNoWhitespace = fmt("{2}.$inject = {3};", null, null, name, ctx.stringify(ctx, params, ctx.quot));
ctx.fragments.push({
start: existingExpressionStatementWithArray.range[0],
end: existingExpressionStatementWithArray.range[1],
str: strNoWhitespace,
loc: {
start: existingExpressionStatementWithArray.loc.start,
end: existingExpressionStatementWithArray.loc.end
}
});
} else if (ctx.mode === "remove" && existingExpressionStatementWithArray) {
const start = skipPrevNewline(existingExpressionStatementWithArray.range[0], existingExpressionStatementWithArray.loc.start);
ctx.fragments.push({
start: start.pos,
end: existingExpressionStatementWithArray.range[1],
str: "",
loc: {
start: start.loc,
end: existingExpressionStatementWithArray.loc.end
}
});
} else if (is.someof(ctx.mode, ["add", "rebuild"]) && !existingExpressionStatementWithArray) {
const str = fmt("{0}{1}{2}.$inject = {3};", EOL, indent, name, ctx.stringify(ctx, params, ctx.quot));
ctx.fragments.push({
start: posAfterFunctionDeclaration.pos,
end: posAfterFunctionDeclaration.pos,
str: str,
loc: {
start: posAfterFunctionDeclaration.loc,
end: posAfterFunctionDeclaration.loc
}
});
}
}
}
function jumpOverIife(node) {
let outerfn;
if (!(node.type === "CallExpression" && (outerfn = node.callee).type === "FunctionExpression")) {
return node;
}
const outerbody = outerfn.body.body;
for (let i = 0; i < outerbody.length; i++) {
const statement = outerbody[i];
if (statement.type === "ReturnStatement") {
return statement.argument;
}
}
return node;
}
function addModuleContextDependentSuspect(target, ctx) {
ctx.suspects.push(target);
}
function addModuleContextIndependentSuspect(target, ctx) {
target.$chained = chainedRegular;
ctx.suspects.push(target);
}
function isAnnotatedArray(node) {
if (node.type !== "ArrayExpression") {
return false;
}
const elements = node.elements;
// last should be a function expression
if (elements.length === 0 || last(elements).type !== "FunctionExpression") {
return false;
}
// all but last should be string literals
for (let i = 0; i < elements.length - 1; i++) {
const n = elements[i];
if (n.type !== "Literal" || !is.string(n.value)) {
return false;
}
}
return true;
}
function isFunctionExpressionWithArgs(node) {
return node.type === "FunctionExpression" && node.params.length >= 1;
}
function isFunctionDeclarationWithArgs(node) {
return node.type === "FunctionDeclaration" && node.params.length >= 1;
}
function isGenericProviderName(node) {
return node.type === "Literal" && is.string(node.value);
}
function uniqifyFragments(fragments) {
// must do in-place modification of ctx.fragments because shared reference
const map = Object.create(null);
for (let i = 0; i < fragments.length; i++) {
const frag = fragments[i];
const str = JSON.stringify({start: frag.start, end: frag.end, str: frag.str});
if (map[str]) {
fragments.splice(i, 1); // remove
i--;
} else {
map[str] = true;
}
}
}
const allOptionals = {
"angular-dashboard-framework": optionalAngularDashboardFramework,
};
module.exports = function ngAnnotate(src, options) {
if (options.list) {
return {
list: Object.keys(allOptionals).sort(),
};
}
const mode = (options.add && options.remove ? "rebuild" :
options.remove ? "remove" :
options.add ? "add" : null);
if (!mode) {
return {src: src};
}
const quot = options.single_quotes ? "'" : '"';
const re = (options.regexp ? new RegExp(options.regexp) : /^[a-zA-Z0-9_\$\.\s]+$/);
const rename = new stringmap();
if (options.rename) {
options.rename.forEach(function(value) {
rename.set(value.from, value.to);
});
}
let ast;
const stats = {};
// detect newline and override os.EOL
const lf = src.lastIndexOf("\n");
if (lf >= 1) {
EOL = (src[lf - 1] === "\r" ? "\r\n" : "\n");
}
// [{type: "Block"|"Line", value: str, range: [from,to]}, ..]
let comments = [];
try {
stats.parser_require_t0 = require_acorn_t0;
stats.parser_require_t1 = require_acorn_t1;
stats.parser_parse_t0 = Date.now();
// acorn
ast = parser(src, {
ecmaVersion: 6,
allowReserved: true,
locations: true,
ranges: true,
onComment: comments,
});
stats.parser_parse_t1 = Date.now();
} catch(e) {
return {
errors: ["error: couldn't process source due to parse error", e.message],
};
}
// append a dummy-node to ast so that lut.findNodeFromPos(lastPos) returns something
ast.body.push({
type: "DebuggerStatement",
range: [ast.range[1], ast.range[1]],
loc: {
start: ast.loc.end,
end: ast.loc.end
}
});
// all source modifications are built up as operations in the
// fragments array, later sent to alter in one shot
const fragments = [];
// suspects is built up with suspect nodes by match.
// A suspect node will get annotations added / removed if it
// fulfills the arrayexpression or functionexpression look,
// and if it is in the correct context (inside an angular
// module definition)
const suspects = [];
// blocked is an array of blocked suspects. Any target node
// (final, i.e. IIFE-jumped, reference-followed and such) included
// in blocked will be ignored by judgeSuspects
const blocked = [];
// Position information for all nodes in the AST,
// used for sourcemap generation
const nodePositions = [];
const lut = new Lut(ast, src);
scopeTools.setupScopeAndReferences(ast);
const ctx = {
mode: mode,
quot: quot,
src: src,
srcForRange: function(range) {
return src.slice(range[0], range[1]);
},
re: re,
rename: rename,
comments: comments,
fragments: fragments,
suspects: suspects,
blocked: blocked,
lut: lut,
isFunctionExpressionWithArgs: isFunctionExpressionWithArgs,
isFunctionDeclarationWithArgs: isFunctionDeclarationWithArgs,
isAnnotatedArray: isAnnotatedArray,
addModuleContextDependentSuspect: addModuleContextDependentSuspect,
addModuleContextIndependentSuspect: addModuleContextIndependentSuspect,
stringify: stringify,
nodePositions: nodePositions,
matchResolve: matchResolve,
matchProp: matchProp,
last: last,
};
// setup optionals
const optionals = options.enable || [];
for (let i = 0; i < optionals.length; i++) {
const optional = String(optionals[i]);
if (!allOptionals.hasOwnProperty(optional)) {
return {
errors: ["error: found no optional named " + optional],
};
}
}
const optionalsPlugins = optionals.map(function(optional) {
return allOptionals[optional];
});
const plugins = [].concat(optionalsPlugins, 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);
ngInject.inspectComments(ctx);
plugins.forEach(function(plugin) {
plugin.init(ctx);
});
traverse(ast, {pre: function(node) {
ngInject.inspectNode(node, ctx);
}, post: function(node) {
ctx.nodePositions.push(node.loc.start);
let targets = match(node, ctx, matchPluginsOrNull);
if (!targets) {
return;
}
if (!is.array(targets)) {
targets = [targets];
}
for (let i = 0; i < targets.length; i++) {
addModuleContextDependentSuspect(targets[i], ctx);
}
}});
try {
judgeSuspects(ctx);
} catch(e) {
return {
errors: ["error: " + e],
};
}
uniqifyFragments(ctx.fragments);
const out = alter(src, fragments);
const result = {
src: out,
_stats: stats,
};
if (options.map) {
if (typeof(options.map) !== 'object')
options.map = {};
stats.sourcemap_t0 = Date.now();
generateSourcemap(result, src, nodePositions, fragments, options.map);
stats.sourcemap_t1 = Date.now();
}
return result;
}