(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
return mod(require("../lib/infer"), require("../lib/tern"), require("../lib/comment"),
require("acorn-walk"));
if (typeof define == "function" && define.amd) // AMD
return define(["../lib/infer", "../lib/tern", "../lib/comment", "acorn-walk/dist/walk"], mod);
mod(tern, tern, tern.comment, acorn.walk);
})(function(infer, tern, comment, walk) {
"use strict";
var SetDoc = infer.constraint({
construct: function(doc) { this.doc = doc; },
addType: function(type) {
if (!type.doc) type.doc = this.doc;
}
});
function Injector() {
this.fields = Object.create(null);
this.forward = [];
}
Injector.prototype.get = function(name) {
if (name == "$scope") return new infer.Obj(globalInclude("$rootScope").getType(), "$scope");
if (name in this.fields) return this.fields[name];
var field = this.fields[name] = new infer.AVal;
return field;
};
Injector.prototype.set = function(name, val, doc, node, depth) {
if (name == "$scope" || depth && depth > 10) return;
var field = this.fields[name] || (this.fields[name] = new infer.AVal);
if (!depth) field.local = true;
if (!field.origin) field.origin = infer.cx().curOrigin;
if (typeof node == "string" && !field.span) field.span = node;
else if (node && typeof node == "object" && !field.originNode) field.originNode = node;
if (doc) { field.doc = doc; field.propagate(new SetDoc(doc)); }
val.propagate(field);
for (var i = 0; i < this.forward.length; ++i)
this.forward[i].set(name, val, doc, node, (depth || 0) + 1);
};
Injector.prototype.forwardTo = function(injector) {
this.forward.push(injector);
for (var field in this.fields) {
var val = this.fields[field];
injector.set(field, val, val.doc, val.span || val.originNode, 1);
}
};
function globalInclude(name) {
var service = infer.cx().definitions.angular.service;
if (service.hasProp(name)) return service.getProp(name);
}
function getInclude(mod, name) {
var glob = globalInclude(name);
if (glob) return glob;
if (!mod.injector) return infer.ANull;
return mod.injector ? mod.injector.get(name) : infer.ANull;
}
function applyWithInjection(mod, fnType, node, asNew) {
var deps = [];
if (/FunctionExpression/.test(node.type)) {
for (var i = 0; i < node.params.length; ++i)
deps.push(getInclude(mod, node.params[i].name));
} else if (node.type == "ArrayExpression") {
for (var i = 0; i < node.elements.length - 1; ++i) {
var elt = node.elements[i];
if (elt.type == "Literal" && typeof elt.value == "string")
deps.push(getInclude(mod, elt.value));
else
deps.push(infer.ANull);
}
var last = node.elements[node.elements.length - 1];
if (last && /FunctionExpression/.test(last.type))
fnType = last.scope.fnType;
}
var result = new infer.AVal;
if (asNew) {
var self = new infer.AVal;
fnType.propagate(new infer.IsCtor(self));
self.propagate(result, 90);
fnType.propagate(new infer.IsCallee(self, deps, null, new infer.IfObj(result)));
} else {
fnType.propagate(new infer.IsCallee(infer.cx().topScope, deps, null, result));
}
return result;
}
infer.registerFunction("angular_callInject", function(argN) {
return function(self, args, argNodes) {
var mod = self.getType();
if (mod && argNodes && argNodes[argN])
applyWithInjection(mod, args[argN], argNodes[argN]);
};
});
infer.registerFunction("angular_regFieldCall", function(self, args, argNodes) {
var mod = self.getType();
if (mod && argNodes && argNodes.length > 1) {
var result = applyWithInjection(mod, args[1], argNodes[1]);
if (mod.injector && argNodes[0].type == "Literal")
mod.injector.set(argNodes[0].value, result, argNodes[0].angularDoc, argNodes[0]);
}
});
infer.registerFunction("angular_regFieldNew", function(self, args, argNodes) {
var mod = self.getType();
if (mod && argNodes && argNodes.length > 1) {
var result = applyWithInjection(mod, args[1], argNodes[1], true);
if (mod.injector && argNodes[0].type == "Literal")
mod.injector.set(argNodes[0].value, result, argNodes[0].angularDoc, argNodes[0]);
}
});
infer.registerFunction("angular_regField", function(self, args, argNodes) {
var mod = self.getType();
if (mod && mod.injector && argNodes && argNodes[0] && argNodes[0].type == "Literal" && args[1])
mod.injector.set(argNodes[0].value, args[1], argNodes[0].angularDoc, argNodes[0]);
});
function arrayNodeToStrings(node) {
var strings = [];
if (node && node.type == "ArrayExpression")
for (var i = 0; i < node.elements.length; ++i) {
var elt = node.elements[i];
if (elt.type == "Literal" && typeof elt.value == "string")
strings.push(elt.value);
}
return strings;
}
function moduleProto(cx) {
var ngDefs = cx.definitions.angular;
return ngDefs && ngDefs.Module.getProp("prototype").getType();
}
function declareMod(name, includes) {
var cx = infer.cx(), data = cx.parent.mod.angular;
var proto = moduleProto(cx);
var mod = new infer.Obj(proto || true);
if (!proto) data.nakedModules.push(mod);
mod.origin = cx.curOrigin;
mod.injector = new Injector();
mod.metaData = {includes: includes};
for (var i = 0; i < includes.length; ++i) {
var depMod = data.modules[includes[i]];
if (!depMod)
(data.pendingImports[includes[i]] || (data.pendingImports[includes[i]] = [])).push(mod.injector);
else if (depMod.injector)
depMod.injector.forwardTo(mod.injector);
}
if (typeof name == "string") {
data.modules[name] = mod;
var pending = data.pendingImports[name];
if (pending) {
delete data.pendingImports[name];
for (var i = 0; i < pending.length; ++i)
mod.injector.forwardTo(pending[i]);
}
}
return mod;
}
infer.registerFunction("angular_module", function(_self, _args, argNodes) {
var mod, name = argNodes && argNodes[0] && argNodes[0].type == "Literal" && argNodes[0].value;
if (typeof name == "string")
mod = infer.cx().parent.mod.angular.modules[name];
if (!mod)
mod = declareMod(name, arrayNodeToStrings(argNodes && argNodes[1]));
return mod;
});
var IsBound = infer.constraint({
construct: function(self, args, target) {
this.self = self; this.args = args; this.target = target;
},
addType: function(tp) {
if (!(tp instanceof infer.Fn)) return;
this.target.addType(new infer.Fn(tp.name, tp.self, tp.args.slice(this.args.length),
tp.argNames.slice(this.args.length), tp.retval));
this.self.propagate(tp.self);
for (var i = 0; i < Math.min(tp.args.length, this.args.length); ++i)
this.args[i].propagate(tp.args[i]);
}
});
infer.registerFunction("angular_bind", function(_self, args) {
if (args.length < 2) return infer.ANull;
var result = new infer.AVal;
args[1].propagate(new IsBound(args[0], args.slice(2), result));
return result;
});
function postParse(ast, text) {
walk.simple(ast, {
CallExpression: function(node) {
if (node.callee.type == "MemberExpression" &&
!node.callee.computed && node.arguments.length &&
/^(value|constant|controller|factory|provider)$/.test(node.callee.property.name)) {
var before = comment.commentsBefore(text, node.callee.property.start - 1);
if (before) {
var first = before[0], dot = first.search(/\.\s/);
if (dot > 5) first = first.slice(0, dot + 1);
first = first.trim().replace(/\s*\n\s*\*\s*|\s{1,}/g, " ");
node.arguments[0].angularDoc = first;
}
}
}
});
}
function postLoadDef(json) {
var cx = infer.cx(), defName = json["!name"], defs = cx.definitions[defName];
if (defName == "angular") {
var proto = moduleProto(cx), naked = cx.parent.mod.angular.nakedModules;
if (proto) for (var i = 0; i < naked.length; ++i) naked[i].proto = proto;
return;
}
var mods = defs && defs["!ng"];
if (mods) for (var name in mods.props) {
var obj = mods.props[name].getType();
var mod = declareMod(name.replace(/`/g, "."), obj.metaData && obj.metaData.includes || []);
mod.origin = defName;
for (var prop in obj.props) {
var val = obj.props[prop], tp = val.getType();
if (!tp) continue;
if (/^_inject_/.test(prop)) {
if (!tp.name) tp.name = prop.slice(8);
mod.injector.set(prop.slice(8), tp, val.doc, val.span);
} else {
obj.props[prop].propagate(mod.defProp(prop));
}
}
}
}
function preCondenseReach(state) {
var mods = infer.cx().parent.mod.angular.modules;
var modObj = new infer.Obj(null), found = 0;
for (var name in mods) {
var mod = mods[name];
if (state.origins.indexOf(mod.origin) > -1) {
var propName = name.replace(/\./g, "`");
modObj.defProp(propName).addType(mod);
mod.condenseForceInclude = true;
++found;
if (mod.injector) for (var inj in mod.injector.fields) {
var field = mod.injector.fields[inj];
if (field.local) state.roots["!ng." + propName + "._inject_" + inj] = field;
}
}
}
if (found) state.roots["!ng"] = modObj;
}
function postCondenseReach(state) {
var mods = infer.cx().parent.mod.angular.modules;
for (var path in state.types) {
var m;
if (m = path.match(/^!ng\.([^\.]+)\._inject_([^\.]+)$/)) {
var mod = mods[m[1].replace(/`/g, ".")];
var field = mod.injector.fields[m[2]];
var data = state.types[path];
if (field.span) data.span = field.span;
if (field.doc) data.doc = field.doc;
}
}
}
function initServer(server) {
server.mod.angular = {
modules: Object.create(null),
pendingImports: Object.create(null),
nakedModules: []
};
}
tern.registerPlugin("angular", function(server) {
initServer(server);
server.on("reset", function() { initServer(server); });
server.on("postParse", postParse);
server.on("postLoadDef", postLoadDef);
server.on("preCondenseReach", preCondenseReach);
server.on("postCondenseReach", postCondenseReach);
server.addDefs(defs, true);
});
var defs = {
"!name": "angular",
"!define": {
cacheObj: {
info: "fn() -> ?",
put: "fn(key: string, value: ?) -> !1",
get: "fn(key: string) -> ?",
remove: "fn(key: string)",
removeAll: "fn()",
destroy: "fn()"
},
eventObj: {
targetScope: "service.$rootScope",
currentScope: "service.$rootScope",
name: "string",
stopPropagation: "fn()",
preventDefault: "fn()",
defaultPrevented: "bool"
},
directiveObj: {
multiElement: {
"!type": "bool",
"!url": "https://docs.angularjs.org/api/ng/service/$compile#-multielement-",
"!doc": "When this property is set to true, the HTML compiler will collect DOM nodes between nodes with the attributes directive-name-start and directive-name-end, and group them together as the directive elements. It is recommended that this feature be used on directives which are not strictly behavioural (such as ngClick), and which do not manipulate or replace child nodes (such as ngInclude)."
},
priority: {
"!type": "number",
"!url": "https://docs.angularjs.org/api/ng/service/$compile#-priority-",
"!doc": "When there are multiple directives defined on a single DOM element, sometimes it is necessary to specify the order in which the directives are applied. The priority is used to sort the directives before their compile functions get called. Priority is defined as a number. Directives with greater numerical priority are compiled first. Pre-link functions are also run in priority order, but post-link functions are run in reverse order. The order of directives with the same priority is undefined. The default priority is 0."
},
terminal: {
"!type": "bool",
"!url": "https://docs.angularjs.org/api/ng/service/$compile#-terminal-",
"!doc": "If set to true then the current priority will be the last set of directives which will execute (any directives at the current priority will still execute as the order of execution on same priority is undefined). Note that expressions and other directives used in the directive's template will also be excluded from execution."
},
scope: {
"!type": "?",
"!url": "https://docs.angularjs.org/api/ng/service/$compile#-scope-",
"!doc": "If set to true, then a new scope will be created for this directive. If multiple directives on the same element request a new scope, only one new scope is created. The new scope rule does not apply for the root of the template since the root of the template always gets a new scope. If set to {} (object hash), then a new 'isolate' scope is created. The 'isolate' scope differs from normal scope in that it does not prototypically inherit from the parent scope. This is useful when creating reusable components, which should not accidentally read or modify data in the parent scope."
},
bindToController: {
"!type": "bool",
"!url": "https://docs.angularjs.org/api/ng/service/$compile#-bindtocontroller-",
"!doc": "When an isolate scope is used for a component (see above), and controllerAs is used, bindToController: true will allow a component to have its properties bound to the controller, rather than to scope. When the controller is instantiated, the initial values of the isolate scope bindings are already available."
},
controller: {
"!type": "fn()",
"!url": "https://docs.angularjs.org/api/ng/service/$compile#-controller-",
"!doc": "Controller constructor function. The controller is instantiated before the pre-linking phase and it is shared with other directives (see require attribute). This allows the directives to communicate with each other and augment each other's behavior."
},
require: {
"!type": "string",
"!url": "https://docs.angularjs.org/api/ng/service/$compile#-require-",
"!doc": "Require another directive and inject its controller as the fourth argument to the linking function. The require takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the injected argument will be an array in corresponding order. If no such directive can be found, or if the directive does not have a controller, then an error is raised."
},
controllerAs: {
"!type": "string",
"!url": "https://docs.angularjs.org/api/ng/service/$compile#-controlleras-",
"!doc": "Controller alias at the directive scope. An alias for the controller so it can be referenced at the directive template. The directive needs to define a scope for this configuration to be used. Useful in the case when directive is used as component."
},
restrict: {
"!type": "string",
"!url": "https://docs.angularjs.org/api/ng/service/$compile#-restrict-",
"!doc": "String of subset of EACM which restricts the directive to a specific directive declaration style. If omitted, the defaults (elements and attributes) are used. E - Element name (default): . A - Attribute (default):
. C - Class: . M - Comment: "
},
templateNamespace: {
"!type": "string",
"!url": "https://docs.angularjs.org/api/ng/service/$compile#-templatenamespace-",
"!doc": "String representing the document type used by the markup in the template. AngularJS needs this information as those elements need to be created and cloned in a special way when they are defined outside their usual containers like