зеркало из https://github.com/mozilla/gecko-dev.git
288 строки
9.6 KiB
JavaScript
Executable File
288 строки
9.6 KiB
JavaScript
Executable File
// 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 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, byName) {
|
|
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;
|
|
data.byName = data.byName == null ? !!byName : data.byName && byName;
|
|
state.types[newPath] = data;
|
|
}
|
|
} else {
|
|
if (relevant) state.altPaths[newPath] = actual;
|
|
}
|
|
}
|
|
function reachByName(aval, path, id, state) {
|
|
var type = aval.getType();
|
|
if (type) reach(type, path, id, state, true);
|
|
}
|
|
|
|
infer.Prim.prototype.reached = function() {return true;};
|
|
|
|
infer.Arr.prototype.reached = function(path, state, concrete) {
|
|
if (!concrete) reachByName(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)
|
|
reachByName(this.args[i], path, "!" + i, state);
|
|
reachByName(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, defs = state.output["!define"];
|
|
for (var i = 0, path; i < parts.length; ++i) {
|
|
var part = parts[i], known = path && state.types[path];
|
|
path = path ? path + "." + part : part;
|
|
var me = state.types[path];
|
|
if (part.charAt(0) == "!" ||
|
|
known && known.type.constructor != infer.Obj ||
|
|
me && me.byName) {
|
|
if (hop(defs, path)) base = defs[path];
|
|
else defs[path] = base = {};
|
|
} else {
|
|
if (hop(base, parts[i])) base = base[part];
|
|
else base = base[part] = {};
|
|
}
|
|
}
|
|
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();
|
|
if (last[0] == "!") return;
|
|
var known = state.types[parts.join(".")];
|
|
var base = createPath(parts, state);
|
|
if (known && known.type.constructor != infer.Obj) return;
|
|
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(prop);
|
|
props.sort();
|
|
for (var i = 0; i < props.length; ++i) {
|
|
var prop = props[i];
|
|
out[prop] = obj[prop];
|
|
}
|
|
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));
|
|
}
|
|
});
|