UNPKG

short-jsdoc

Version:

short and simple jsdoc Object Oriented syntax format and implementation

295 lines (244 loc) 7.72 kB
// @module shortjsdoc @class JsDocMaker var JsDocMaker = require('./class'); var _ = require('underscore'); // BINDING / post processing //@class TypeBinding a datatype with an association between types names in source code and parsed class nodes. //It support generic types (recursive) //@property {TypeBinding} type //@property {Array<TypeBinding>} params - the generic types params array. For example the params for {Map<String,Apple>} is [StringBynding] //@property {Object<String,TypeBinding>} properties - the properties literal object declaration binding, {a:A,b:B} //@property {String} nativeTypeUrl the url for native type only //@class JsDocMaker //@method parseTypeString public, do a type binding @return {TypeBinding} the object binding to the original r //eferenced AST node. Or null in case the given type cannot be parsed //TODO: support multiple generics and generics anidation like in JsDocMaker.prototype.parseTypeString = function(typeString, baseClass) { if(!typeString || !baseClass) { return null; } //first remove the '{}' typeString = JsDocMaker.stringFullTrim(typeString); var inner = /^{([^}]+)}$/.exec(typeString); if(!inner || inner.length<2) { return null; } typeString = inner[1]; typeString = typeString.replace(/\s+/gi, ''); var ret = this.parseSingleTypeString(typeString, baseClass); // console.log('parseTypeString', ret) if(ret && ret.length===1) { return ret[0]; } else { return ret; } }; // @method parseSingleTypeString @param {String} typeStr JsDocMaker.prototype.parseSingleTypeString = function(typeStr, baseClass) { var a = typeStr.split('|'), ret = [], self = this; _(a).each(function(typeString) { // is this a custom type, like #custom(1,2) ? var regex = /^#(\w+)\(([^\()]+)\)/ , customType = regex.exec(typeString) , type_binded = null , type = null; if(customType && customType.length === 3) { var parserName = customType[1]; var parserInput = customType[2]; var parser = self.typeParsers[parserName]; if(parser) { try { var parsed = parser.parse(parserInput, baseClass); if(parsed) { // TODO bind type ? //BIG PROBLEM HERE - this code executes at parsing time and here we are binding - do this binding in a post processing ast //TODO probably all this code should be moved to postprocessing ast phase and here we only dump the original type string. type_binded = self.bindParsedType(parsed, baseClass); ret.push(type_binded); } } catch(ex) { self.error('Invalid Custom Type: '+typeString, ', baseClass: ', (baseClass && baseClass.absoluteName)); } } } //it is a literal object type, like {a:String,b:Number}? else if(typeString.indexOf(':')!==-1 && typeString.indexOf('#')===-1 ) //and is not a custom type #cus { type = null; try { var props = JsDocMaker.parseType(typeString); type = {name: 'Object', properties: props}; type_binded = self.bindParsedType(type, baseClass); ret.push(type_binded); } catch(ex) { self.error('Invalid Type: '+typeString, ', baseClass: ', (baseClass && baseClass.absoluteName)); } } // it is a generic type like Array<String> ? else if(typeString.indexOf('<')!==-1) { type = null; try { type = JsDocMaker.parseType(typeString); type_binded = self.bindParsedType(type, baseClass); ret.push(type_binded); } catch(ex) { self.error('Invalid Type: '+typeString, ', baseClass: ', (baseClass && baseClass.absoluteName)); } } else { ret.push(self.bindClass(typeString, baseClass)); } }) ; return ret; }; //@method bindParsedType merges the data of JsDocMaker.parseType with bindings of current jsdoc. recursive! //@param {Object} typeObject @param {Object} baseClass @return {Object} JsDocMaker.prototype.bindParsedType = function(typeObject, baseClass) { var c = null , out = typeObject , self = this; if(typeObject && _(typeObject).isString()) { c = this.bindClass(typeObject, baseClass); out = {name: typeObject}; } else if(typeObject && typeObject.name) { //recurse on params for generic types like M<T,K>! if(out.params) { var new_params = []; c = this.bindClass(typeObject.name, baseClass); _(out.params).each(function(param) { var new_param = self.bindParsedType(param, baseClass); new_params.push(new_param); }); out.params = new_params; } //recurse on properties for literal object type like name:String,config:Config if(out.properties) { var new_properties = {}; _(out.properties).each(function(value, name) { var new_property = self.bindParsedType(value, baseClass); new_properties[name] = new_property; }); out.properties = new_properties; } } if(c) { _(out).extend(c); } return out; }; var PluginContainer = require('./plugin'); //POST PROCESSING // @property {PluginContainer} beforeBindClassPlugins these plugins accept an object like // {name:name,baseClass:JsDocASTNode,jsdocmaker:JsDocMaker} and perform some modification to passed node:parsed instance. // This is done just before a class name is binding to an actual AST class node. JsDocMaker.prototype.beforeBindClassPlugins = new PluginContainer(); // @property {PluginContainer} afterBindClassPlugins these plugins accept an object like // {name:name,baseClass:JsDocASTNode,jsdocmaker:JsDocMaker} and perform some modification to passed node:parsed instance. // This is done just after a class name is binding to an actual AST class node. JsDocMaker.prototype.afterBindClassPlugins = new PluginContainer(); //@method bindClass @param {String}name @param {Object} baseClass //TODO: using a internal map this could be done faster JsDocMaker.prototype.bindClass = function(name, baseClass) { var context = { name:name , baseClass: baseClass , jsdocmaker: this }; this.beforeBindClassPlugins.execute(context); // beforeBindClassPlugins have the oportunity of changing the context name = context.name || name; baseClass = context.baseClass || baseClass; var moduleName = baseClass.annotation === 'module' ? baseClass.name : baseClass.module.name; //search all classes that matches the name var classesWithName = _(_(this.data.classes).values()).filter(function(c) { return c.name===name;//JsDocMaker.stringEndsWith(c.name, name); }); //search classes of the module var moduleClasses = _(classesWithName).filter(function(c) { return JsDocMaker.startsWith(c.module.name, moduleName); }); //TODO: performance - classesWithName could be compauted only if moduleClasses is empty var c = moduleClasses.length ? moduleClasses[0] : classesWithName[0]; if(!c) { //then it could be a function var fns = [] _.each(this.data.modules, function(module) { _.each(module.functions, function(fn) { if(fn.name===name) { fns.push(fn) } }) }); if(fns.length) { c = fns[0] } } if(!c) { //TODO: look at native types var nativeType = this.getNativeTypeUrl ? this.getNativeTypeUrl(name) : null; var o = {name:name}; if(nativeType) { o.nativeTypeUrl = nativeType; } else { o.error = 'NAME_NOT_FOUND'; } c = o; } this.afterBindClassPlugins.execute({name:name, binded: c, baseClass: baseClass, jsdocmaker: this}); return c; }; // @method simpleName @param {String} name @return {String} JsDocMaker.prototype.simpleName = function(name, prefix) { if(prefix && name.indexOf(prefix) === 0) { return name.substring(prefix.length + 1, name.length); } else { var a = name.split(JsDocMaker.ABSOLUTE_NAME_SEPARATOR); return a[a.length - 1]; } };