UNPKG

tern-aui2.0.x

Version:

A Tern plugin adding AlloyUI 2.x support.

555 lines (512 loc) 20.8 kB
(function(root, mod) { if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS if (typeof define == "function" && define.amd) return define([ "exports" ], mod); // AMD mod(root.yuidoc2tern || (root.yuidoc2tern = {})); // Plain browser env })(this,function(exports) { "use strict"; var Generator = exports.Generator = function(options) { this.options = options; }; Generator.prototype.process = function(yuiDoc) { var ternDef = { "!name" : this.options.name, "!define" : { "config": {} } }; this.options.initialize(ternDef); this.visitDoc(yuiDoc, ternDef); return ternDef; }; Generator.prototype.visitDoc = function(yuiDoc, ternDef) { // Iterate over all class items for ( var i = 0; i < yuiDoc.classitems.length; i++) { var yuiClassItem = yuiDoc.classitems[i]; if (!this.isIgnoreClassItem(yuiClassItem) && isAccess(yuiClassItem, this.options.isSubModule)) { if (isEventType(yuiClassItem)) { // TODO : add event inside !data } else { var moduleName = getModuleName(yuiClassItem, yuiDoc, true), className = yuiClassItem["class"], attributeType = isAttributeType(yuiClassItem), isStaticMethod = (isStatic(yuiClassItem) || attributeType); // case of DataTable.BodyView.Formatters which is a Class and object property var isObjectAndClassBoth = (yuiClassItem["type"] == "Object") && yuiClassItem["itemtype"] == "property" && yuiDoc.classes[className + "." + yuiClassItem["name"]]; if (isObjectAndClassBoth) className = className + "." + yuiClassItem["name"]; if (moduleName) { var ternModule = getTernModule(moduleName, ternDef, this.options.isSubModule, yuiDoc); var ternClass = null; if (isGlobal(yuiClassItem)) { ternClass = ternDef; } else { ternClass = attributeType ? getTernClassConfig(className, ternDef["!define"], yuiDoc) : this.getTernClass(className, ternModule, moduleName.replace(/-/g, '_'), yuiDoc, null, ternDef); } if (!isObjectAndClassBoth) { var ternClassItem = isStaticMethod ? ternClass : getTernClassPrototype(ternClass); this.visitClassItem(yuiClassItem, yuiDoc, ternClassItem, ternDef); } } } } } } function isGlobal(yuiClassItem) { return yuiClassItem.name == "YUI_config"; } Generator.prototype.isIgnoreClassItem = function(yuiClassItem) { return this.options.isIgnoreClassItem ? this.options.isIgnoreClassItem(yuiClassItem) : false; } Generator.prototype.visitClassItem = function(yuiClassItem, yuiDoc, ternClassItem, ternDef) { var moduleName = getModuleName(yuiClassItem, yuiDoc), className = yuiClassItem["class"], name = yuiClassItem["name"]; // !type var type = this.getTernType(yuiClassItem, yuiDoc, ternDef); // !proto var proto = null; var effects = this.options.getEffects ? this.options.getEffects(moduleName, className, name, !isStatic(yuiClassItem)) : null; // !doc var doc = getDescription(yuiClassItem); // !url var url = this.options.baseURL ? getURL(this.options.baseURL, className, yuiClassItem.itemtype, name) : null; // !data var submodule = yuiClassItem["submodule"]; var data = this.options.getData ? this.options.getData(moduleName, className, name, !isStatic(yuiClassItem)) : null; if (submodule) { if (!data) data = {}; data["submodule"] = submodule; } createTernDefItem(ternClassItem, name, type, proto, effects, url, doc, data); } Generator.prototype.getTernType = function(yuiClass, yuiDoc, ternDef) { var moduleName = getModuleName(yuiClass, yuiDoc), className = yuiClass["class"] ? yuiClass["class"] : yuiClass["name"], name = yuiClass["class"] ? yuiClass["name"] : null; var overridedType = this.options.getType ? this.options.getType(moduleName, className, name, !isStatic(yuiClass)) : null; if (overridedType) return overridedType; return getTernType(yuiClass, yuiDoc, this.options.isSubModule, ternDef); } Generator.prototype.getTernClass = function(className, parent, moduleName, yuiDoc, fullClassName, ternDef) { // get name var name = className; if (className.indexOf('.') != -1) { var names = className.split('.'), length = names.length -1; var locFullClassName = ""; for (var i = 0; i < length; i++) { if (i > 0) locFullClassName+="."; locFullClassName+=names[i]; parent = this.getTernClass(names[i], parent, moduleName, yuiDoc, locFullClassName, ternDef); } name = names[length]; } var ternClass = parent[name]; if (!ternClass) { var yuiClass = fullClassName ? yuiDoc.classes[fullClassName] : yuiDoc.classes[className], type, proto, effects, doc, url, data; if (!yuiClass) yuiClass = yuiDoc.classes[className] if (yuiClass) { // !proto proto = this.getProto(yuiClass, yuiDoc, true); effects = null; // !doc doc = null; // if (yuiClass.description) // ternClass["!doc"] = yuiClass.description; // !url url = this.options.baseURL ? getURL(this.options.baseURL, className) : null; var uses = yuiClass["uses"], forClass = this.getForClass(className, moduleName, yuiDoc), augments = [], exts = []; if (!forClass) { // !type type = this.getTernType(yuiClass, yuiDoc, ternDef); } if (uses) { for (var i = 0; i < uses.length; i++) { var useClassName = uses[i], useFullClassName = getClassName(useClassName, yuiDoc, this.options.isSubModule); if (useFullClassName && useFullClassName != forClass) { if (isExtensionFor(useClassName, className, yuiDoc)) { exts.push(useFullClassName); } else { augments.push(useFullClassName); } } } } if (augments.length > 0 || exts.length > 0 || forClass) { data = {}; if (augments.length > 0) data["augments"] = augments; if (exts.length > 0) data["extends"] = exts; if (forClass) data["for"] = forClass; } } ternClass = createTernDefItem(parent, name, type, proto, effects, url, doc, data); } return ternClass; } Generator.prototype.getForClass = function(className, moduleName, yuiDoc) { var forCLass = this.options.getForClass ? this.options.getForClass(className) : null; if (forCLass) return forCLass; var yuiClass = yuiDoc.classes[className]; var forClassModuleName = yuiClass ? yuiClass["module"] && yuiClass["module"].replace(/-/g, '_') : null; if (forClassModuleName && forClassModuleName != moduleName) return forClassModuleName + "." + className; } var getTernClassConfig = function(className, parent, yuiDoc) { var names = getConfigType(className).split("."); var ternClass = parent; for (var i = 0; i < names.length; i++) { var name = names[i]; ternClass = parent[name]; if (!ternClass) ternClass = parent[name] = {}; parent = ternClass; } var yuiClass = yuiDoc.classes[className]; if (yuiClass) { var yuiExtends = yuiClass["extends"]; if (yuiExtends) ternClass["!proto"] = getConfigType(yuiExtends); /*var proto = this.getProto(yuiClass, yuiDoc); if (proto) { ternClass["!proto"] = getConfigType(proto); }*/ } return ternClass; } var getTernClassPrototype = function(ternClass) { var ternPrototype = ternClass["prototype"]; if (!ternPrototype) ternClass["prototype"] = ternPrototype = {}; return ternPrototype; } var isAccess = function(yuiClassItem, isSubModule) { if (isSubModule && yuiClassItem.file && (startsWith(yuiClassItem.file, "yui3") || startsWith(yuiClassItem.file, "lib/yui3"))) { return false; } var access = yuiClassItem["access"]; return access != 'private' && access != 'protected'; } var isStatic = function(yuiClassItem) { return yuiClassItem["static"] === 1 || "YUI_config" == yuiClassItem.name; } var isEventType = function(yuiClassItem) { var itemtype = yuiClassItem["itemtype"]; return itemtype === 'event'; } var isAttributeType = function(yuiClassItem) { var itemtype = yuiClassItem["itemtype"]; return itemtype === 'attribute'; } var isExtensionFor = function(useClassName, className, yuiDoc) { var clazz = yuiDoc.classes[useClassName], extension_for = clazz ? clazz.extension_for : null; if (extension_for) { for (var i = 0; i < extension_for.length; i++) { if (extension_for[i] == className) return true; } } return false; } var getDescription = function(yuiClassItem) { var description = yuiClassItem["description"]; if (!description) return null; description = description.replace(/['$']/g, ""); return description; } var getURL = function(baseURL, className, itemtype, name) { var url = baseURL; if (!endsWith(baseURL, '/')) { url += '/'; } url += 'classes/'; url += className; url += '.html'; if (itemtype && name) { url += '#'; url += itemtype; url += '_'; url += name; } return url; } function isEmpty(obj) { return Object.keys(obj).length === 0; } var getTernModule = function(moduleName, ternDef, isSubModule, yuiDoc) { // YUI module uses '-' in their name, and tern cannot support that, replace '-' with '_' var name = moduleName.replace(/-/g, '_'); var parent = ternDef["!define"]; if (isSubModule) { var sub = parent["_yui"]; if (!sub) sub = parent["_yui"] = {}; parent = sub; } var ternModule = parent[name]; if (!ternModule) { // create module ternModule = parent[name]= {}; var data = {}, mods = yuiDoc.modules, mod = mods ? mods[moduleName] : null; if (name != moduleName ) data["module"] = moduleName; if (mod && mod.submodules) { var submodules = {}; for(var submodule in mod.submodules) { submodules[submodule] = {}; } if (!isEmpty(submodules)) { if (!data) data = {}; data["submodules"] = submodules; } } if (!isEmpty(data)) ternModule["!data"] = data; } return ternModule; } var createTernDefItem = function(parent, name, type, proto, effects, url, doc, data) { var item = parent[name] = {}; if (type) item["!type"] = type; if (effects) item["!effects"] = effects; if (url) item["!url"] = url; if (doc && doc != '') item["!doc"] = doc; if (data) item["!data"] = data; if (proto) getTernClassPrototype(item)["!proto"] = proto; return item; } // YUI -> Tern type var getFirstPart = function(yuiType, c) { var index = yuiType.indexOf(c); if (index != -1) { yuiType = yuiType.substring(0, index); yuiType = yuiType.trim(); } return yuiType; } var extractYUIType = exports.extractYUIType= function(yuiType) { if (!yuiType) return null; //yuiType = yuiType.trim(); var index = -1; if (startsWith(yuiType, '{')) { index = yuiType.indexOf('}'); yuiType = yuiType.substring(1, index != -1 ? index : yuiType.length); } var yuiTypes = null; if (yuiType.indexOf("/") != -1) { yuiTypes = yuiType.split("/"); } else { yuiTypes = yuiType.split("|"); } var filterYuiTypes = []; for (var i = 0; i < yuiTypes.length; i++) { var t = yuiTypes[i].trim(); // ex : {ArrayList|Widget} or {Any} if (startsWith(t, '{')) { index = t.indexOf('}'); t = t.substring(1, index != -1 ? index : t.length); } // ex : {string: boolean} t = getFirstPart(t, ':'); // ex : Object* t = t.replace(/[*]/g, ''); t = t.trim(); if (t.length > 0 && t != "null" && t != "undefined") filterYuiTypes.push(t); } return filterYuiTypes; } var getTernType = exports.getTernType = function(yuiClass, yuiDoc, isSubModule, ternDef) { var itemtype = yuiClass["itemtype"]; if (itemtype == 'config' && yuiClass.params) { // case for EventTarget which has params and itemtype=config (and type=Boolean) // we force it to method. itemtype = 'method'; } switch(itemtype) { case 'method': var className = yuiClass["class"], methodName = yuiClass.name, params = yuiClass.params, returnValue = yuiClass["return"], isChainable = yuiClass["chainable"] === 1, isConstructor = yuiClass["is_constructor"] === 1; var name = className + methodName.substring(0, 1).toUpperCase() + methodName.substring(1, methodName.length); return getFunctionTernType(name, params, returnValue, isChainable, isConstructor, yuiDoc, isSubModule, ternDef); break; case 'property': case 'attribute': var yuiType = yuiClass.type; return getPropertyTernType(yuiType, null, yuiDoc, ternDef); case 'event': break; case 'config': var yuiType = yuiClass.type; return getPropertyTernType(yuiType, null, yuiDoc, ternDef); default: var className = yuiClass.name, params = yuiClass.params, returnValue = yuiClass["return"], isChainable = yuiClass["chainable"] === 1, isConstructor = yuiClass["is_constructor"] === 1; return getFunctionTernType(className, params, returnValue, isChainable, isConstructor, yuiDoc, isSubModule, ternDef); } } var getFunctionTernType = function(className, params, returnValue, isChainable, isConstructor, yuiDoc, isSubModule, ternDef) { var type = 'fn('; if (params) { for ( var i = 0; i < params.length; i++) { var param = params[i], name = toTernName(param.name); if (i > 0) type += ', '; type += name; if (param.optional) type += '?'; type += ': '; if (param.type) { if (param.type == 'Object') { if (param.props && param.props.length > 0) { // param Object with properties var paramObjClass = null; if (className == null) { var index = 0, paramObjName = getConfigType("param" + index); while(true) { paramObjClass = getTernClassConfig("param" + index, ternDef["!define"], yuiDoc); if (isEmpty(paramObjClass)) { break; } index++; paramObjName = getConfigType("param" + index); } } else { paramObjClass = getTernClassConfig(className, ternDef["!define"], yuiDoc); paramObjName = getConfigType(className); } for (var j = 0; j < param.props.length; j++) { var prop = param.props[j], paramObjItem = paramObjClass[prop.name] = {}, paramDoc = getDescription(prop), paramType = getPropertyTernType(prop.type, null, yuiDoc, ternDef); if (paramType) paramObjItem["!type"] = paramType; if (paramDoc) paramObjItem["!doc"] = paramDoc; } type += "+" + paramObjName; } else if (param.name == 'config') { // case for config Object Literal (filled with attribute itemtype) type += "+" + getConfigType(className); } else { type += "+Object"; } } else { type += getPropertyTernType(param.type, param.props, yuiDoc, isSubModule, ternDef); } } else { type += '?'; } } } type += ')'; if (isChainable) { type += ' -> !this'; } /*else if (isConstructor) { type += ' -> +'; type += getClassName(className, yuiDoc, isSubModule); }*/ else if (returnValue) { type += ' -> '; type += getPropertyTernType(returnValue.type, returnValue.props, yuiDoc, ternDef); } return type; } var getConfigType = function(className) { var index = className.lastIndexOf('.'), name = index !=- 1 ? className.substring(index+1, className.length) : className; return "config." + className + "Config"; } var getPropertyTernType = exports.getPropertyTernType = function(yuiType, props, yuiDoc, isSubModule, ternDef) { if (!yuiType) return "?"; var types = "", yuiTypes = extractYUIType(yuiType); if (yuiTypes) { for (var i = 0; i < yuiTypes.length; i++) { var type = toTernType(yuiTypes[i], props, yuiDoc, isSubModule, ternDef); if (type) { if (startsWith(type, "fn(")) { if (i == 0) return type; continue; } if (types.length > 0) types+= "|"; types+= type; } } } return types.length > 0 ? types : "?"; } function toTernType(type, props, yuiDoc, isSubModule, ternDef) { // var type = extractYUIType(yuiType); // if (!type) return null; // is array? var isArray = false, index = type.indexOf('['); if (index > 0) { type = type.substring(0, index); isArray = true; } type = type.trim(); switch (type.toLowerCase()) { case 'function': return getFunctionTernType(null, props, null, false, false, yuiDoc, isSubModule, ternDef); case 'any': return '?'; case 'object': return formatType('Object', isArray, true); case 'array': return '[?]'; case 'null': return null; case 'string': return formatType('string', isArray); case 'number': case 'int': case 'num': case 'float': return formatType('number', isArray); case 'boolean': case 'false': case 'true': return formatType('bool', isArray); default: return formatType(getClassName(type, yuiDoc, isSubModule), isArray, true); } } var getModuleName = function(yuiClassItem, yuiDoc, dontReplace) { //var className = yuiClassItem["class"]; //var yuiClass = yuiDoc.classes[className]; var moduleName = /*yuiClass ? yuiClass["module"] :*/ yuiClassItem["module"]; return dontReplace ? moduleName : moduleName.replace(/-/g, '_'); } var getClassName = function(className, yuiDoc, isSubModule) { var yuiClass = yuiDoc.classes[className]; if (yuiClass && yuiClass.module) { var name = yuiClass.module.replace(/-/g, '_') + '.' + className; return name; } return className; } Generator.prototype.getProto = function(yuiClassItem, yuiDoc, withPrototype) { var yuiExtends = yuiClassItem["extends"]; if (!yuiExtends) { return this.options.getProto ? this.options.getProto() : null; } var className = getClassName(yuiExtends, yuiDoc, this.options.isSubModule) return withPrototype ? (className + '.prototype') : className; } var toTernName = exports.toTernName = function(yuiName) { var name = yuiName; name = name.replace(/-/g, ''); name = extractName(name, '*'); name = extractName(name, ','); // ex : @param name = extractName(name, '@'); // ex : prepend=false name = extractName(name, '='); // ex : + if (name == '+' || name.length == 0) return "arg"; return name; } function extractName(name, c) { var index = name.indexOf(c); if (index == -1) return name; if (index == 0) return name.substring(1, name.length); return name.substring(0, index); } var formatType = function (type, isArray, isInstance) { var t = ""; if (isArray) { t += '['; } if (isInstance && type != 'string' && type != 'bool' && type != 'number') t += '+'; t += type; if (isArray) { t += ']'; } return t; } var startsWith = function(str, prefix) { return str.slice(0, prefix.length) == prefix; } var endsWith = function(str, suffix) { return str.slice(-suffix.length) == suffix; } });