Skip to content

Instantly share code, notes, and snippets.

@jameskeane
Created July 20, 2014 03:31
Show Gist options
  • Save jameskeane/4ed3aef8ce99da104bf0 to your computer and use it in GitHub Desktop.
Save jameskeane/4ed3aef8ce99da104bf0 to your computer and use it in GitHub Desktop.
closure library for ternjs
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
return mod(require("../lib/infer"), require("../lib/tern"));
if (typeof define == "function" && define.amd) // AMD
return define(["../lib/infer", "../lib/tern"], mod);
mod(tern, tern);
})(function(infer, tern) {
"use strict";
var WG_TEMP_OBJ = 40;
function resolvePath(base, path) {
if (path[0] == "/") return path;
var slash = base.lastIndexOf("/"), m;
if (slash >= 0) path = base.slice(0, slash + 1) + path;
while (m = /[^\/]*[^\/\.][^\/]*\/\.\.\//.exec(path))
path = path.slice(0, m.index) + path.slice(m.index + m[0].length);
return path.replace(/(^|[^\.])\.\//g, "$1");
}
function normPath(name) { return name.replace(/\\/g, "/"); }
function resolveProjectPath(server, pth) {
return resolvePath(normPath(server.options.projectDir) + "/", normPath(pth));
}
function each(arr, fn) {
for (var i = 0; i < arr.length; i++) fn(arr[i], i, arr);
}
function map(arr, fn) {
var r = [];
each(arr, function(el, i, arr) { r.push(fn(el, i, arr)); });
return r;
}
function defineFQN(name, server) {
var parts = name.split('.');
var base = infer.cx().topScope;
var prop;
for (var i = 0; i < parts.length; i++) {
prop = base.defProp(parts[i]);
if (prop.getType()) {
base = prop.getType();
} else {
base = new infer.Obj(true, parts.slice(0, i + 1).join('.'));
// Use a custom origin to prevent the purging of stand-in types on file
// reload.
base.origin = 'closure';
base.propagate(prop, WG_TEMP_OBJ);
}
}
// Try to load the file providing the name.
var provide = server._closure.provides[name];
if (provide) {
server.addFile(provide.path, null, server._closure.currentOrigin);
}
return prop;
}
/**
* Build up the dependency tree by reading the dep files.
*/
infer.registerFunction("closureBuildDepTree", function(_self, _args, argNodes) {
if (!argNodes || !argNodes.length || argNodes.length < 3) return;
var cx = infer.cx(), server = cx.parent, data = server._closure,
file = argNodes[0].value;
var path = resolveProjectPath(server, resolvePath(data.currentFile, file));
var requires = map(argNodes[2].elements, function(node) {
return node.value;
});
var provides = map(argNodes[1].elements, function(node) {
return node.value;
});
// build up the defines
data.modules[path] = {
path: path,
provides: provides,
requires: requires
};
each(provides, function(def) {
data.provides[def] = data.modules[path];
});
});
function getInterface(name, data) {
return data.interfaces[name] || (data.interfaces[name] = new infer.AVal());
}
/**
* goog.provide implicitly defines the object structure.
* i.e. goog.provide('tern.closure.test') -> var tern = {closure: { test: {} }};
*/
infer.registerFunction("closureProvide", function(_self, _args, argNodes) {
var cx = infer.cx(), server = cx.parent, data = server._closure,
name = argNodes[0].value, scope = argNodes[0].sourceFile.scope;
defineFQN(name, server);
});
/**
* goog.require implicitly defines the object structure and links the final
* part to the interface.
*/
infer.registerFunction("closureRequire", function(_self, _args, argNodes) {
var cx = infer.cx(), server = cx.parent, data = server._closure,
name = argNodes[0].value, scope = argNodes[0].sourceFile.scope;
defineFQN(name, server);
});
/**
* Set up the inheritence tree.
*/
infer.registerFunction("closureInherits", function(_self, _args, argNodes) {
var child = _args[0].getType(), parent = _args[0].getType();
if (!(child instanceof infer.Fn) || !(parent instanceof infer.Fn)) return;
// create a fn type that returns an instance of the obj
var proto = new infer.AVal();
parent.getProp('prototype').propagate(proto);
child.defProp('prototype').addType(proto);
});
/**
* Add a singleton getter to the obj.
*/
infer.registerFunction("closureAddSingletonGetter", function(_self, _args, argNodes) {
var tp = _args[0].getType();
if (!(tp instanceof infer.Fn)) return;
// create a fn type that returns an instance of the obj
var instance = new infer.AVal();
tp.getProp('prototype').propagate(instance);
instance.origin = tp.origin;
var sngl = new infer.Fn('getInstance', infer.ANull, [], [], instance);
tp.defProp('getInstance').addType(sngl);
});
tern.registerPlugin("closure", function(server, options) {
server._closure = {
modules: Object.create(null),
provides: Object.create(null),
provided: [],
interfaces: Object.create(null),
options: options || {},
currentFile: null,
currentOrigin: null,
server: server
};
server.on("beforeLoad", function(file) {
var path = this._closure.currentFile = resolveProjectPath(server, file.name);
var data = this._closure;
this._closure.currentOrigin = file.name;
});
server.on("afterLoad", function(file) {
var path = this._closure.currentFile = resolveProjectPath(server, file.name);
this._closure.currentFile = null;
this._closure.currentOrigin = null;
this._closure.provided = [];
});
server.on("reset", function() {
this._closure.modules = Object.create(null);
this._closure.provides = Object.create(null);
this._closure.interfaces = Object.create(null);
});
// If pre loaded with deps files try to parse them
(options.deps || []).forEach(function(depfile) {
server.addFile(depfile);
});
return {
defs: defs
};
});
var defs = {
"!name": "goog",
goog: {
addDependency: {
"!effects": ["custom closureBuildDepTree"],
"!type": "fn(path: string, defines: [string], dependencies: [string])"
},
provide: {
"!effects": ["custom closureProvide"],
"!type": "fn(id: string)",
},
require: {
"!effects": ["custom closureRequire"],
"!type": "fn(id: string)",
},
inherits: {
"!effects": ["custom closureInherits"],
"!type": "fn(obj: fn(), parent: fn())"
},
addSingletonGetter: {
"!effects": ["custom closureAddSingletonGetter"],
"!type": "fn(obj: fn())"
}
}
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment