(function (mod){ if (typeof exports == "object" && typeof module == "object") return mod(require("infer"), require("tern"), require("comment"), require("walk")); if (typeof define == "function" && define.amd) return define(["infer", "tern", "comment", "walk"] , mod); mod(tern, tern, tern.comment, acorn.walk); } )(function (infer, tern, comment, walk){ "use strict"; tern.registerPlugin("doc_comment", function (){ return { passes: { "postParse": postParse, "postInfer": postInfer} } ; } ); function postParse(ast, text){ function attachComments(node){ comment.ensureCommentsBefore(text, node); } walk.simple(ast, { VariableDeclaration: attachComments, FunctionDeclaration: attachComments, AssignmentExpression: function (node){ if (node.operator == "=") attachComments(node); } , ObjectExpression: function (node){ for (var i = 0; i < _AN_Read_length("length", node.properties); ++i)attachComments(node.properties[i].key); } } ); } function postInfer(ast, scope){ walk.simple(ast, { VariableDeclaration: function (node, scope){ if (node.commentsBefore) interpretComments(node, node.commentsBefore, scope, scope.getProp(node.declarations[0].id.name)); } , FunctionDeclaration: function (node, scope){ if (node.commentsBefore) interpretComments(node, node.commentsBefore, scope, scope.getProp(node.id.name), node.body.scope.fnType); } , AssignmentExpression: function (node, scope){ if (node.commentsBefore) interpretComments(node, node.commentsBefore, scope, infer.expressionType({ node: node.left, state: scope} )); } , ObjectExpression: function (node, scope){ for (var i = 0; i < _AN_Read_length("length", node.properties); ++i){ var prop = node.properties[i], key = prop.key; if (key.commentsBefore) interpretComments(prop, key.commentsBefore, scope, node.objType.getProp(key.name)); } } } , infer.searchVisitor, scope); } function interpretComments(node, comments, scope, aval, type){ jsdocInterpretComments(node, scope, aval, comments); if (!type && aval instanceof infer.AVal && _AN_Read_length("length", aval.types)) { type = aval.types[_AN_Read_length("length", aval.types) - 1]; if (!(type instanceof infer.Obj) || type.origin != infer.cx().curOrigin || type.doc) type = null ; } var first = comments[0], dot = first.search(/\.\s/); if (dot > 5) first = first.slice(0, dot + 1); first = _AN_Call_replace("replace", first.trim(), /\s*\n\s*\*\s*|\s{1,}/g, " "); if (aval instanceof infer.AVal) aval.doc = first; if (type) type.doc = first; } function skipSpace(str, pos){ while (/\s/.test(str.charAt(pos)))++pos; return pos; } function parseLabelList(scope, str, pos, close){ var labels = [] , types = [] ; for (var first = true ; ; first = false ){ pos = skipSpace(str, pos); if (first && str.charAt(pos) == close) break ; var colon = str.indexOf(":", pos); if (colon < 0) return null ; var label = str.slice(pos, colon); if (!/^[\w$]+$/.test(label)) return null ; labels.push(label); pos = colon + 1; var type = parseType(scope, str, pos); if (!type) return null ; pos = type.end; types.push(type.type); pos = skipSpace(str, pos); var next = str.charAt(pos); ++pos; if (next == close) break ; if (next != ",") return null ; } return { labels: labels, types: types, end: pos} ; } function parseType(scope, str, pos){ pos = skipSpace(str, pos); var type; if (str.indexOf("function(", pos) == pos) { var args = parseLabelList(scope, str, pos + 9, ")"), ret = infer.ANull; if (!args) return null ; pos = skipSpace(str, args.end); if (str.charAt(pos) == ":") { ++pos; var retType = parseType(scope, str, pos + 1); if (!retType) return null ; pos = retType.end; ret = retType.type; } type = new infer.Fn(null , infer.ANull, args.types, args.labels, ret); } else if (str.charAt(pos) == "[") { var inner = parseType(scope, str, pos + 1); if (!inner) return null ; pos = skipSpace(str, inner.end); if (str.charAt(pos) != "]") return null ; ++pos; type = new infer.Arr(inner.type); } else if (str.charAt(pos) == "{") { var fields = parseLabelList(scope, str, pos + 1, "}"); if (!fields) return null ; type = new infer.Obj(true ); for (var i = 0; i < _AN_Read_length("length", fields.types); ++i){ var field = type.defProp(fields.labels[i]); field.initializer = true ; fields.types[i].propagate(field); } pos = fields.end; } else { var start = pos; while (/[\w$]/.test(str.charAt(pos)))++pos; if (start == pos) return null ; var word = str.slice(start, pos); if (/^(number|integer)$/i.test(word)) type = infer.cx().num; else if (/^bool(ean)?$/i.test(word)) type = infer.cx().bool; else if (/^string$/i.test(word)) type = infer.cx().str; else if (/^array$/i.test(word)) { var inner = null ; if (str.charAt(pos) == "." && str.charAt(pos + 1) == "<") { var inAngles = parseType(scope, str, pos + 2); if (!inAngles) return null ; pos = skipSpace(str, inAngles.end); if (str.charAt(pos++ ) != ">") return null ; inner = inAngles.type; } type = new infer.Arr(inner); } else if (/^object$/i.test(word)) { type = new infer.Obj(true ); if (str.charAt(pos) == "." && str.charAt(pos + 1) == "<") { var key = parseType(scope, str, pos + 2); if (!key) return null ; pos = skipSpace(str, key.end); if (str.charAt(pos++ ) != ",") return null ; var val = parseType(scope, str, pos); if (!val) return null ; pos = skipSpace(str, val.end); if (str.charAt(pos++ ) != ">") return null ; val.type.propagate(type.defProp("")); } } else { var found = scope.hasProp(word); if (found) found = found.getType(); if (!found) { type = infer.ANull; } else if (found instanceof infer.Fn && /^[A-Z]/.test(word)) { var proto = found.getProp("prototype").getType(); if (proto instanceof infer.Obj) type = infer.getInstance(proto); else type = found; } else { type = found; } } } var isOptional = false ; if (str.charAt(pos) == "=") { ++pos; isOptional = true ; } return { type: type, end: pos, isOptional: isOptional} ; } function parseTypeOuter(scope, str, pos){ pos = skipSpace(str, pos || 0); if (str.charAt(pos) != "{") return null ; var result = parseType(scope, str, pos + 1); if (!result || str.charAt(result.end) != "}") return null ; ++result.end; return result; } function jsdocInterpretComments(node, scope, aval, comments){ var type, args, ret, foundOne; for (var i = 0; i < _AN_Read_length("length", comments); ++i){ var comment = comments[i]; var decl = /(?:\n|$|\*)\s*@(type|param|arg(?:ument)?|returns?)\s+(.*)/g, m; while (m = decl.exec(comment)){ var parsed = parseTypeOuter(scope, m[2]); if (!parsed) continue ; foundOne = true ; switch (m[1]){ case "returns": case "return": ret = parsed.type; break ; case "type": type = parsed.type; break ; case "param": case "arg": case "argument": var name = m[2].slice(parsed.end).match(/^\s*([\w$]+)/); if (!name) continue ; var argname = name[1] + (parsed.isOptional? "?": ""); (args || (args = Object.create(null )))[argname] = parsed.type; break ; } } } if (foundOne) applyType(type, args, ret, node, aval); } ; function applyType(type, args, ret, node, aval){ var fn; if (node.type == "VariableDeclaration") { var decl = node.declarations[0]; if (decl.init && decl.init.type == "FunctionExpression") fn = decl.init.body.scope.fnType; } else if (node.type == "FunctionDeclaration") { fn = node.body.scope.fnType; } else if (node.type == "AssignmentExpression") { if (node.right.type == "FunctionExpression") fn = node.right.body.scope.fnType; } else { if (node.value.type == "FunctionExpression") fn = node.value.body.scope.fnType; } if (fn && (args || ret)) { if (args) for (var i = 0; i < _AN_Read_length("length", fn.argNames); ++i){ var name = fn.argNames[i], known = args[name]; if (!known && (known = args[name + "?"])) fn.argNames[i] += "?"; if (known) known.propagate(fn.args[i]); } if (ret) ret.propagate(fn.retval); } else if (type) { type.propagate(aval); } } ; } );