gecko/toolkit/devtools/tern/condense.js

283 lines
9.4 KiB
JavaScript
Raw Normal View History

// Condensing an inferred set of types to a JSON description document.
// This code can be used to, after a library has been analyzed,
// extract the types defined in that library and dump them as a JSON
// structure (as parsed by def.js).
// The idea being that big libraries can be analyzed once, dumped, and
// then cheaply included in later analysis.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
return mod(exports, require("./infer"));
if (typeof define == "function" && define.amd) // AMD
return define(["exports", "./infer"], mod);
mod(self.tern || (self.tern = {}), tern); // Plain browser env
})(function(exports, infer) {
"use strict";
exports.condense = function(origins, name, options) {
if (typeof origins == "string") origins = [origins];
var state = new State(origins, name || origins[0], options || {});
runPass(state.passes.preCondenseReach, state);
state.cx.topScope.path = "<top>";
state.cx.topScope.reached("", state);
for (var path in state.roots)
reach(state.roots[path], null, path, state);
for (var i = 0; i < state.patchUp.length; ++i)
patchUpSimpleInstance(state.patchUp[i], state);
runPass(state.passes.postCondenseReach, state);
for (var path in state.types)
store(createPath(path.split("."), state), state.types[path], state);
for (var path in state.altPaths)
storeAlt(path, state.altPaths[path], state);
var hasDef = false;
for (var _def in state.output["!define"]) { hasDef = true; break; }
if (!hasDef) delete state.output["!define"];
runPass(state.passes.postCondense, state);
return simplify(state.output, state.options.sortOutput);
};
function State(origins, name, options) {
this.origins = origins;
this.cx = infer.cx();
this.passes = options.passes || this.cx.parent && this.cx.parent.passes || {};
this.maxOrigin = -Infinity;
for (var i = 0; i < origins.length; ++i)
this.maxOrigin = Math.max(this.maxOrigin, this.cx.origins.indexOf(origins[i]));
this.output = {"!name": name, "!define": {}};
this.options = options;
this.types = Object.create(null);
this.altPaths = Object.create(null);
this.patchUp = [];
this.roots = Object.create(null);
}
State.prototype.isTarget = function(origin) {
return this.origins.indexOf(origin) > -1;
};
State.prototype.getSpan = function(node) {
if (this.options.spans == false || !this.isTarget(node.origin)) return null;
if (node.span) return node.span;
var srv = this.cx.parent, file;
if (!srv || !node.originNode || !(file = srv.findFile(node.origin))) return null;
var start = node.originNode.start, end = node.originNode.end;
var pStart = file.asLineChar(start), pEnd = file.asLineChar(end);
return start + "[" + pStart.line + ":" + pStart.ch + "]-" +
end + "[" + pEnd.line + ":" + pEnd.ch + "]";
};
function pathLen(path) {
var len = 1, pos = 0, dot;
while ((dot = path.indexOf(".", pos)) != -1) {
pos = dot + 1;
len += path.charAt(pos) == "!" ? 10 : 1;
}
return len;
}
function isConcrete(path) {
return !/\!|<i>/.test(path);
}
function hop(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
function isSimpleInstance(o) {
return o.proto && !(o instanceof infer.Fn) && o.proto != infer.cx().protos.Object &&
o.proto.hasCtor && !o.hasCtor;
}
function reach(type, path, id, state) {
var actual = type.getType(false);
if (!actual) return;
var orig = type.origin || actual.origin, relevant = false;
if (orig) {
var origPos = state.cx.origins.indexOf(orig);
// This is a path that is newer than the code we are interested in.
if (origPos > state.maxOrigin) return;
relevant = state.isTarget(orig);
}
var newPath = path ? path + "." + id : id, oldPath = actual.path;
var shorter = !oldPath || pathLen(oldPath) > pathLen(newPath);
if (shorter) {
if (!(actual instanceof infer.Prim)) actual.path = newPath;
if (actual.reached(newPath, state, !relevant) && relevant) {
var data = state.types[oldPath];
if (data) {
delete state.types[oldPath];
state.altPaths[oldPath] = actual;
} else data = {type: actual};
data.span = state.getSpan(type) || (actual != type && state.isTarget(actual.origin) && state.getSpan(actual)) || data.span;
data.doc = type.doc || (actual != type && state.isTarget(actual.origin) && type.doc) || data.doc;
data.data = actual.metaData;
state.types[newPath] = data;
}
} else {
if (relevant) state.altPaths[newPath] = actual;
}
}
function reachTypeOnly(aval, path, id, state) {
var type = aval.getType();
if (type) reach(type, path, id, state);
}
infer.Prim.prototype.reached = function() {return true;};
infer.Arr.prototype.reached = function(path, state, concrete) {
if (!concrete) reachTypeOnly(this.getProp("<i>"), path, "<i>", state);
return true;
};
infer.Fn.prototype.reached = function(path, state, concrete) {
infer.Obj.prototype.reached.call(this, path, state, concrete);
if (!concrete) {
for (var i = 0; i < this.args.length; ++i)
reachTypeOnly(this.args[i], path, "!" + i, state);
reachTypeOnly(this.retval, path, "!ret", state);
}
return true;
};
infer.Obj.prototype.reached = function(path, state, concrete) {
if (isSimpleInstance(this) && !this.condenseForceInclude) {
if (state.patchUp.indexOf(this) == -1) state.patchUp.push(this);
return true;
} else if (this.proto && !concrete) {
reach(this.proto, path, "!proto", state);
}
var hasProps = false;
for (var prop in this.props) {
reach(this.props[prop], path, prop, state);
hasProps = true;
}
if (!hasProps && !this.condenseForceInclude && !(this instanceof infer.Fn)) {
this.nameOverride = "?";
return false;
}
return true;
};
function patchUpSimpleInstance(obj, state) {
var path = obj.proto.hasCtor.path;
if (path) {
obj.nameOverride = "+" + path;
} else {
path = obj.path;
}
for (var prop in obj.props)
reach(obj.props[prop], path, prop, state);
}
function createPath(parts, state) {
var base = state.output;
for (var i = parts.length - 1; i >= 0; --i) if (!isConcrete(parts[i])) {
var def = parts.slice(0, i + 1).join(".");
var defs = state.output["!define"];
if (hop(defs, def)) base = defs[def];
else defs[def] = base = {};
parts = parts.slice(i + 1);
}
for (var i = 0; i < parts.length; ++i) {
if (hop(base, parts[i])) base = base[parts[i]];
else base = base[parts[i]] = {};
}
return base;
}
function store(out, info, state) {
var name = typeName(info.type);
if (name != info.type.path && name != "?") {
out["!type"] = name;
} else if (info.type.proto && info.type.proto != state.cx.protos.Object) {
var protoName = typeName(info.type.proto);
if (protoName != "?") out["!proto"] = protoName;
}
if (info.span) out["!span"] = info.span;
if (info.doc) out["!doc"] = info.doc;
if (info.data) out["!data"] = info.data;
}
function storeAlt(path, type, state) {
var parts = path.split("."), last = parts.pop();
var base = createPath(parts, state);
if (!hop(base, last)) base[last] = type.nameOverride || type.path;
}
var typeNameStack = [];
function typeName(type) {
var actual = type.getType(false);
if (!actual || typeNameStack.indexOf(actual) > -1)
return actual && actual.path || "?";
typeNameStack.push(actual);
var name = actual.typeName();
typeNameStack.pop();
return name;
}
infer.Prim.prototype.typeName = function() { return this.name; };
infer.Arr.prototype.typeName = function() {
return "[" + typeName(this.getProp("<i>")) + "]";
};
infer.Fn.prototype.typeName = function() {
var out = "fn(";
for (var i = 0; i < this.args.length; ++i) {
if (i) out += ", ";
var name = this.argNames[i];
if (name && name != "?") out += name + ": ";
out += typeName(this.args[i]);
}
out += ")";
if (this.computeRetSource) {
out += " -> " + this.computeRetSource;
} else if (!this.retval.isEmpty()) {
var rettype = this.retval.getType(false);
if (rettype) out += " -> " + typeName(rettype);
}
return out;
};
infer.Obj.prototype.typeName = function() {
if (this.nameOverride) return this.nameOverride;
if (!this.path) return "?";
return this.path;
};
function simplify(data, sort) {
if (typeof data != "object") return data;
var sawType = false, sawOther = false;
for (var prop in data) {
if (prop == "!type") sawType = true;
else sawOther = true;
if (prop != "!data")
data[prop] = simplify(data[prop], sort);
}
if (sawType && !sawOther) return data["!type"];
return sort ? sortObject(data) : data;
}
function sortObject(obj) {
var props = [], out = {};
for (var prop in obj) props.push({name: prop, val: obj[prop]});
props.sort(function(a, b) { return a.name < b.name ? -1 : 1; });
for (var i = 0; i < props.length; ++i)
out[props[i].name] = props[i].val;
return out;
}
function runPass(functions) {
if (functions) for (var i = 0; i < functions.length; ++i)
functions[i].apply(null, Array.prototype.slice.call(arguments, 1));
}
});