123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- // Parses comments above variable declarations, function declarations,
- // and object properties as docstrings and JSDoc-style type
- // annotations.
- (function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- return mod(require("../lib/infer"), require("../lib/tern"), require("../lib/comment"),
- require("acorn"), require("acorn/dist/walk"));
- if (typeof define == "function" && define.amd) // AMD
- return define(["../lib/infer", "../lib/tern", "../lib/comment", "acorn/dist/acorn", "acorn/dist/walk"], mod);
- mod(tern, tern, tern.comment, acorn, acorn.walk);
- })(function(infer, tern, comment, acorn, walk) {
- "use strict";
- var WG_MADEUP = 1, WG_STRONG = 101;
- tern.registerPlugin("doc_comment", function(server, options) {
- server.jsdocTypedefs = Object.create(null);
- server.on("reset", function() {
- server.jsdocTypedefs = Object.create(null);
- });
- server._docComment = {
- weight: options && options.strong ? WG_STRONG : undefined,
- fullDocs: options && options.fullDocs
- };
- return {
- passes: {
- postParse: postParse,
- postInfer: postInfer,
- postLoadDef: postLoadDef
- }
- };
- });
- 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 < node.properties.length; ++i)
- attachComments(node.properties[i]);
- },
- CallExpression: function(node) {
- if (isDefinePropertyCall(node)) attachComments(node);
- }
- });
- }
- function isDefinePropertyCall(node) {
- return node.callee.type == "MemberExpression" &&
- node.callee.object.name == "Object" &&
- node.callee.property.name == "defineProperty" &&
- node.arguments.length >= 3 &&
- typeof node.arguments[1].value == "string";
- }
- function postInfer(ast, scope) {
- jsdocParseTypedefs(ast.sourceFile.text, 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 < node.properties.length; ++i) {
- var prop = node.properties[i];
- if (prop.commentsBefore)
- interpretComments(prop, prop.commentsBefore, scope,
- node.objType.getProp(prop.key.name));
- }
- },
- CallExpression: function(node, scope) {
- if (node.commentsBefore && isDefinePropertyCall(node)) {
- var type = infer.expressionType({node: node.arguments[0], state: scope}).getObjType();
- if (type && type instanceof infer.Obj) {
- var prop = type.props[node.arguments[1].value];
- if (prop) interpretComments(node, node.commentsBefore, scope, prop);
- }
- }
- }
- }, infer.searchVisitor, scope);
- }
- function postLoadDef(data) {
- var defs = data["!typedef"];
- var cx = infer.cx(), orig = data["!name"];
- if (defs) for (var name in defs)
- cx.parent.jsdocTypedefs[name] =
- maybeInstance(infer.def.parse(defs[name], orig, name), name);
- }
- // COMMENT INTERPRETATION
- function interpretComments(node, comments, scope, aval, type) {
- jsdocInterpretComments(node, scope, aval, comments);
- var cx = infer.cx();
- if (!type && aval instanceof infer.AVal && aval.types.length) {
- type = aval.types[aval.types.length - 1];
- if (!(type instanceof infer.Obj) || type.origin != cx.curOrigin || type.doc)
- type = null;
- }
- var result = comments[comments.length - 1];
- if (cx.parent._docComment.fullDocs) {
- result = result.trim().replace(/\n[ \t]*\* ?/g, "\n");
- } else {
- var dot = result.search(/\.\s/);
- if (dot > 5) result = result.slice(0, dot + 1);
- result = result.trim().replace(/\s*\n\s*\*\s*|\s{1,}/g, " ");
- }
- result = result.replace(/^\s*\*+\s*/, "");
- if (aval instanceof infer.AVal) aval.doc = result;
- if (type) type.doc = result;
- }
- // Parses a subset of JSDoc-style comments in order to include the
- // explicitly defined types in the analysis.
- function skipSpace(str, pos) {
- while (/\s/.test(str.charAt(pos))) ++pos;
- return pos;
- }
- function isIdentifier(string) {
- if (!acorn.isIdentifierStart(string.charCodeAt(0))) return false;
- for (var i = 1; i < string.length; i++)
- if (!acorn.isIdentifierChar(string.charCodeAt(i))) return false;
- return true;
- }
- function parseLabelList(scope, str, pos, close) {
- var labels = [], types = [], madeUp = false;
- 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 (!isIdentifier(label)) return null;
- labels.push(label);
- pos = colon + 1;
- var type = parseType(scope, str, pos);
- if (!type) return null;
- pos = type.end;
- madeUp = madeUp || type.madeUp;
- 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, madeUp: madeUp};
- }
- function parseType(scope, str, pos) {
- var type, union = false, madeUp = false;
- for (;;) {
- var inner = parseTypeInner(scope, str, pos);
- if (!inner) return null;
- madeUp = madeUp || inner.madeUp;
- if (union) inner.type.propagate(union);
- else type = inner.type;
- pos = skipSpace(str, inner.end);
- if (str.charAt(pos) != "|") break;
- pos++;
- if (!union) {
- union = new infer.AVal;
- type.propagate(union);
- type = union;
- }
- }
- var isOptional = false;
- if (str.charAt(pos) == "=") {
- ++pos;
- isOptional = true;
- }
- return {type: type, end: pos, isOptional: isOptional, madeUp: madeUp};
- }
- function parseTypeInner(scope, str, pos) {
- pos = skipSpace(str, pos);
- var type, madeUp = false;
- 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;
- madeUp = retType.madeUp;
- }
- 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);
- madeUp = inner.madeUp;
- 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 < fields.types.length; ++i) {
- var field = type.defProp(fields.labels[i]);
- field.initializer = true;
- fields.types[i].propagate(field);
- }
- pos = fields.end;
- madeUp = fields.madeUp;
- } 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 = inner.type;
- } else {
- var start = pos;
- if (!acorn.isIdentifierStart(str.charCodeAt(pos))) return null;
- while (acorn.isIdentifierChar(str.charCodeAt(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 (/^(null|undefined)$/i.test(word)) type = infer.ANull;
- 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);
- madeUp = inAngles.madeUp;
- 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);
- madeUp = key.madeUp || val.madeUp;
- if (str.charAt(pos++) != ">") return null;
- val.type.propagate(type.defProp("<i>"));
- }
- } else {
- while (str.charCodeAt(pos) == 46 ||
- acorn.isIdentifierChar(str.charCodeAt(pos))) ++pos;
- var path = str.slice(start, pos);
- var cx = infer.cx(), defs = cx.parent && cx.parent.jsdocTypedefs, found;
- if (defs && (path in defs)) {
- type = defs[path];
- } else if (found = infer.def.parsePath(path, scope).getObjType()) {
- type = maybeInstance(found, path);
- } else {
- if (!cx.jsdocPlaceholders) cx.jsdocPlaceholders = Object.create(null);
- if (!(path in cx.jsdocPlaceholders))
- type = cx.jsdocPlaceholders[path] = new infer.Obj(null, path);
- else
- type = cx.jsdocPlaceholders[path];
- madeUp = true;
- }
- }
- }
- return {type: type, end: pos, madeUp: madeUp};
- }
- function maybeInstance(type, path) {
- if (type instanceof infer.Fn && /^[A-Z]/.test(path)) {
- var proto = type.getProp("prototype").getObjType();
- if (proto instanceof infer.Obj) return infer.getInstance(proto);
- }
- return type;
- }
- 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) return null;
- var end = skipSpace(str, result.end);
- if (str.charAt(end) != "}") return null;
- result.end = end + 1;
- return result;
- }
- function jsdocInterpretComments(node, scope, aval, comments) {
- var type, args, ret, foundOne, self, parsed;
- for (var i = 0; i < comments.length; ++i) {
- var comment = comments[i];
- var decl = /(?:\n|\$|\*)\s*@(type|param|arg(?:ument)?|returns?|this)\s+(.*)/g, m;
- while (m = decl.exec(comment)) {
- if (m[1] == "this" && (parsed = parseType(scope, m[2], 0))) {
- self = parsed;
- foundOne = true;
- continue;
- }
- if (!(parsed = parseTypeOuter(scope, m[2]))) continue;
- foundOne = true;
- switch(m[1]) {
- case "returns": case "return":
- ret = parsed; break;
- case "type":
- type = parsed; break;
- case "param": case "arg": case "argument":
- var name = m[2].slice(parsed.end).match(/^\s*(\[?)\s*([^\]\s=]+)\s*(?:=[^\]]+\s*)?(\]?).*/);
- if (!name) continue;
- var argname = name[2] + (parsed.isOptional || (name[1] === '[' && name[3] === ']') ? "?" : "");
- (args || (args = Object.create(null)))[argname] = parsed;
- break;
- }
- }
- }
- if (foundOne) applyType(type, self, args, ret, node, aval);
- };
- function jsdocParseTypedefs(text, scope) {
- var cx = infer.cx();
- var re = /\s@typedef\s+(.*)/g, m;
- while (m = re.exec(text)) {
- var parsed = parseTypeOuter(scope, m[1]);
- var name = parsed && m[1].slice(parsed.end).match(/^\s*(\S+)/);
- if (name)
- cx.parent.jsdocTypedefs[name[1]] = parsed.type;
- }
- }
- function propagateWithWeight(type, target) {
- var weight = infer.cx().parent._docComment.weight;
- type.type.propagate(target, weight || (type.madeUp ? WG_MADEUP : undefined));
- }
- function applyType(type, self, 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.type == "CallExpression") {
- } else { // An object property
- if (node.value.type == "FunctionExpression") fn = node.value.body.scope.fnType;
- }
- if (fn && (args || ret || self)) {
- if (args) for (var i = 0; i < fn.argNames.length; ++i) {
- var name = fn.argNames[i], known = args[name];
- if (!known && (known = args[name + "?"]))
- fn.argNames[i] += "?";
- if (known) propagateWithWeight(known, fn.args[i]);
- }
- if (ret) propagateWithWeight(ret, fn.retval);
- if (self) propagateWithWeight(self, fn.self);
- } else if (type) {
- propagateWithWeight(type, aval);
- }
- };
- });
|