🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
Book a DemoInstallSign in
Socket

getdocs

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

getdocs - npm Package Compare versions

Comparing version

to
0.5.0

test/class_simple.js

2

bin/getdocs.js

@@ -9,3 +9,3 @@ var fs = require("fs")

glob.sync(arg).forEach(function(filename) {
getdocs.gather(fs.readFileSync(filename, "utf8"), filename, items)
getdocs.gather(fs.readFileSync(filename, "utf8"), {filename: filename, items: items})
})

@@ -12,0 +12,0 @@ })

{
"name": "getdocs",
"version": "0.4.0",
"version": "0.5.0",
"description": "An extractor for succinct documentation comments in JavaScript code",

@@ -5,0 +5,0 @@ "main": "src/index.js",

@@ -37,3 +37,2 @@ # Getdocs

"description": "Add two numbers",
"kind": "function",
"exported": true

@@ -44,12 +43,17 @@ }

The idea is to then feed this into a system that massages it into
actual HTML or Markdown or whatever documentation files.
The idea is to then feed this into a system (can be a simple set of
templates) that massages it into actual human-readable documention
files.
A getdocs doc comment starts at either a type declarations (a comment
line starting with `::`) or a start marker `;;`. It can be either a
block comment or a sequence of line comments.
A getdocs doc comment starts with a double colon, optionally prefixed
with a name (`foo::`) and followed by a type. It can be either a block
comment or a continuous sequence of line comments. When you don't want
to specify a type, for example because the type can be inferred from
the code (as with a class declaration), you can write a single dash
after the colons, instead of a type.
Such a doc comment applies to the next program element after it. That
element should be something with a name, like a variable, function, or
class declaration, or an assignment that can be statically resolved.
When no name is given, such a doc comment applies to the next program
element after it. That element should be something with a name, like a
variable, function, or class declaration, or an assignment that can be
statically resolved.

@@ -62,2 +66,22 @@ The documented items found in the files passed to getdocs will be

Inside a doc comment, properties of the thing being defined can be
added by writing nested, indented doc comments. For example:
```
// Plugin:: interface
//
// Objects conforming to the plugin interface can be plugged into a
// Foo
//
// mount:: (Foo) → bool
// Mount the plugin in this Foo. The return value indicates whether
// the mount succeeded.
//
// unmount:: (Foo)
// Unmount the plugin from a Foo.
```
Further nesting below such a property (by adding more indentation) is
supported.
## Type syntax

@@ -69,4 +93,5 @@

properties, which are a dot character followed by a JavaScript
identifier. A type name can be followed by a list of content types,
between angle brackets, as in `Object<string>`.
identifier. A type name can be followed by a list of type
parameters, between angle brackets, as in `Object<string>` (an
object whose properties hold string values).

@@ -89,5 +114,5 @@ * An array type, which is a type wrapped in `[` and `]`. `[x]` is

`}` braces. Each property must start with an identifier, followed
by a comma, followed by a type.
by a colon, followed by a type.
* A string literal, enclosed by double quotes.
* A string literal, enclosed by double quotes, or a number literal.

@@ -106,3 +131,6 @@ Here are some examples of types:

* An array of `CommandSpec`s or the string "schema": `union<[CommandSpec], "schema">`
* An array of numbers or a string: `union<[number], string>` (what
the name `union` means isn't something getdocs is aware of, but
you could use it for union types, and maybe render it as `[number]
| string` in your output).

@@ -113,4 +141,3 @@ ## Tags

prefixed with a `#` character, appearing at the start of the comment —
that is, immediately after the `;;` for a type-less comment, or
immediately after the type for a typed one.
that is, immediately after the type.

@@ -126,23 +153,6 @@ A tag like `#deprecated`, for example, will result in a `$deprecated:

These tags have a special meaning that is interpreted by getdocs:
The `#static` tag can be used to indicate that a given class member is
static (which is only necessary for doc comments that aren't tied to a
syntactic element in the code).
* **path**: Prevents the comment from being associated with the
program element after it, and puts it in the namespace under the
given path instead, which should be something like `name` or
`Foo.prototype.methodName`. You can also separate elements with a
`#` to indicate a direct property (rather than going through
`.properties`) in the output—for example `Foo#constructor` to set
the constructor property of a class.
* **kind**: Explicitly sets the kind of this item. Does not get a
dollar sign prefix.
* **forward**: Can be used to make the properties or methods of a
class or object appear in another class or object. A typical use
case is moving documentation from a private subclass into a public
abstract class. A tag like `#forward=Foo` will cause the properties
of the annotated thing to appear in the documentation for the thing
named `Foo` instead. Note that other information included in the
doc comments that has the `forward` tag will be ignored.
## Output JSON

@@ -156,9 +166,5 @@

* **kind**: The kind of program element that is documented. May be
`function`, `var`, `let`, `const`, `class`, `constructor`,
`method`, `getter`, or `setter`.
* **loc**: A `{line, column, file}` object pointing at the start of the item.
* **exported**: Set if the item is exported.
* **exported**: Set if the item is exported using ES6 module syntax.

@@ -168,7 +174,7 @@ * **constructor**: For classes with a documented constructor, this

* **extends**: Only applies for classes. Holds the name of the
* **extends**: Only applies for classes. Holds the type of the
superclass.
* **instanceProperties**: For classes, this holds properties and
methods that appear on instances (and on the prototype).
* **staticProperties**: For classes, this holds properties and
methods that appear directly on the constructor.

@@ -181,5 +187,7 @@ In addition, they may have these properties, which can also appear on

`Array` or `Function`. Getdocs does not prescribe a naming of
builtin types, but for consistency I recommend you use `number`,
primitive types, but for consistency I recommend you use `number`,
`string`, and `bool`.
* **properties**: An object mapping property names to types.
* **params**: For function types, this holds an array of parameter

@@ -192,3 +200,4 @@ types. Parameter types can have these additional properties:

* **default**: The default value of the parameter.
* **default**: The default value of the parameter (as a raw
source string).

@@ -198,7 +207,42 @@ * **returns**: For function types, this holds the type that is

* **properties**: An object mapping property names to types.
* **typeParams**: For array types or named types with parameters
(angle bracket syntax), this holds an array of parameter types.
* **content**: For array types or named types with content (angle
brackets) specification, this holds an array of content types.
* **optional**: Set for nullable types.
* **optional**: Set for nullable types.
* **id**: The path to this type. For a top-level variable `foo`
this'll be `"foo"`, for the type of the property `bar` under `foo`,
it'll be `"foo.bar"`, and so on.
## Interface
The module exports the following function:
**`gather`**`: (code: string, options: Object) → Object`
It takes a code file, extracts the docs, and returns an object
describing the documented items.
Options can have the following properties:
* **`filename`**`: string` The filename of the given code. Required.
* **`items`**`: ?Object` An existing items object to add the items
found in the given code to.
* **`onComment`**`: ?(block: bool, text: string, start: number, end:
number, startPos: Object, endPos: Object)` Will be called for each
comment in the code, if given.
**`parseType`**`: (input: string, start: number, loc: {file: string, line: number}) → {type: Object, end: number}`
Parse a type in getdocs syntax into its object representation. `start`
indicates where in the string the parsing should start. The returned
object tells you where the type ended.
Will throw a `SyntaxError` when the type isn't valid.
**`stripComment`**`: (comment: string) → string`
Strips leading indentation and asterisks (as in the common block
comment style where each line gets an asterisk) from a string.

@@ -19,2 +19,8 @@ var acorn = require("acorn/dist/acorn")

}
if (head != null) {
var startIndent = /^\s*/.exec(lines[0])[0]
var trailing = /\s*$/.exec(head)[0]
var extra = trailing.length - startIndent.length
if (extra > 0) head = head.slice(0, head.length - extra)
}

@@ -45,5 +51,7 @@ outer: for (var i = 0; i < lines.length; i++) {

exports.parse = function(text, filename) {
var current = null, found = []
exports.stripComment = function(text) { return strip(text.split("\n")) }
exports.parse = function(text, options) {
var current = null, found = [], filename = options.filename
var ast = acorn.parse(text, {

@@ -55,13 +63,14 @@ ecmaVersion: 6,

onComment: function(block, text, start, end, startLoc, endLoc) {
if (/^\s*(?:::|;;)/.test(text)) {
if (current && !block && current.endLoc.line == startLoc.line - 1) {
current.text.push(text)
current.end = end
current.endLoc = endLoc
} else if (/^\s*[\w\.$]*::/.test(text)) {
var obj = {text: text.split("\n"), start: start, end: end, startLoc: startLoc, endLoc: endLoc}
found.push(obj)
if (!block) current = obj
} else if (current && !block && current.endLoc.line == startLoc.line - 1) {
current.text.push(text)
current.end = end
current.endLoc = endLoc
} else {
current = null
}
if (options.onComment) options.onComment(block, text, start, end, startLoc, endLoc)
}

@@ -73,3 +82,3 @@ })

loc.file = filename
comment.data = parseComment(strip(comment.text), comment.startLoc)
comment.parsed = parseNestedComments(strip(comment.text), comment.startLoc)
}

@@ -101,16 +110,27 @@ return {ast: ast, comments: found}

var directTags = {kind: true}
exports.findNodeAround = function(ast, pos, types) {
var stack = [], found
function c(node, _, override) {
if (node.end <= pos || node.start >= pos) return
if (!override) stack.push(node)
walk.base[override || node.type](node, null, c)
if (types[node.type] && !found) found = stack.slice()
if (!override) stack.pop()
}
c(ast)
return found || stack
}
function parseComment(text, loc) {
var match = /^\s*(;;|::)\s*/.exec(text)
var data, pos = match[0].length
if (match[1] == "::") {
var parsed = parseType(text, pos, loc)
data = parsed.type
pos = parsed.end
} else {
var match = /^\s*([\w\.$]+)?::\s*(-\s*)?/.exec(text), data, end = match[0].length, name = match[1]
if (match[2]) {
data = Object.create(null)
data.loc = loc
} else {
var parsed = parseType(text, match[0].length, loc)
data = parsed.type
end = parsed.end
}
text = text.slice(pos)
text = text.slice(end)
while (match = /^\s*#([\w$]+)(?:=([^"]\S*|"(?:[^"\\]|\\.)*"))?\s*/.exec(text)) {

@@ -120,6 +140,35 @@ text = text.slice(match[0].length)

if (value.charAt(0) == '"') value = JSON.parse(value)
data[directTags.hasOwnProperty(match[1]) ? match[1] : "$" + match[1]] = value
data["$" + match[1]] = value
}
if (/\S/.test(text)) data.description = text
return data
return {data: data, name: name, subcomments: []}
}
function parseNestedComments(text, loc) {
var line = 0, context = [], top, nextIndent = /^\s*/.exec(text)[0].length
for (;;) {
var next = /\n( *)[\w\.$]*::/.exec(text)
var current = next ? text.slice(0, next.index) : text
var parsed = parseComment(current, line ? {line: loc.line + line, column: loc.column, file: loc.file} : loc)
if (!top) {
top = parsed
} else {
if (!parsed.name)
throw new SyntaxError("Sub-comment without name at " + loc.file + ":" + (loc.line + line))
while (context[context.length - 1].indent >= nextIndent) {
context.pop()
if (!context.length)
throw new SyntaxError("Invalid indentation for sub-field at " + loc.file + ":" + (loc.line + line))
}
context[context.length - 1].comment.subcomments.push(parsed)
}
context.push({indent: nextIndent, comment: parsed})
if (!next) break
line += current.split("\n").length + 1
text = text.slice(current.length + 1)
nextIndent = next[1].length
}
return top
}
var docComments = require("./doccomments")
var parseType = require("./parsetype")
exports.stripComment = docComments.stripComment
var parseType = exports.parseType = require("./parsetype")
exports.gather = function(text, filename, items) {
if (!items) items = Object.create(null)
exports.gather = function(text, options) {
let items = options.items || Object.create(null)
var top = {properties: items}
var found = docComments.parse(text, filename)
var found = docComments.parse(text, options)
var findPos = findPosFor(items)
found.comments.forEach(function(comment) {
var data = comment.parsed.data
found.comments.forEach(function(comment) {
var data = comment.data
if (data.$path) {
var path = splitPath(data.$path)
posFromPath(items, path).add(data)
if (!data.kind && data.type) {
if (data.type == "Function")
data.kind = path.length > 1 ? "method" : "function"
else
data.kind = path.length > 1 ? "property" : "const"
}
return
if (comment.parsed.name) {
var stack = docComments.findNodeAround(found.ast, comment.end, findPath)
path = addNameToPath(comment.parsed.name, getPath(stack), data.$static)
} else {
var stack = docComments.findNodeAfter(found.ast, comment.end, findPath)
var node = stack && stack[stack.length - 1]
if (!node || !/^(?:[;{},\s]|\/\/.*|\/\*.*?\*\/)*$/.test(text.slice(node.end, comment.start)))
throw new SyntaxError("Misplaced documentation block at " + options.filename + ":" + comment.startLoc.line)
if (inferForNode.hasOwnProperty(node.type)) data = inferForNode[node.type](node, data, stack)
var path = getPath(stack)
}
var stack = docComments.findNodeAfter(found.ast, comment.end, findPos)
var top = stack && stack[stack.length - 1]
if (!top || !/^(?:[;{},\s]|\/\/.*|\/\*.*?\*\/)*$/.test(text.slice(top.end, comment.start)))
throw new SyntaxError("Misplaced documentation block at " + filename + ":" + comment.startLoc.line)
var stored = addData(top, path, data)
if (data.$forward) {
top.forward = splitPath(data.$forward)
} else {
var pos = findPos[top.type](top, stack)
if (inferForNode.hasOwnProperty(top.type)) data = inferForNode[top.type](top, data, stack)
pos.add(data)
}
comment.parsed.subcomments.forEach(function(sub) { applySubComment(stored, sub) })
})

@@ -51,74 +43,132 @@

assignIds(top)
return items
}
function findPosFor(items) {
return {
// FIXME destructuring
VariableDeclaration: function(node) { return new Pos(items, node.declarations[0].id.name) },
function applySubComment(parent, sub) {
var target
if (parent.type == "Function") {
if (sub.name == "return")
target = parent.returns
else if (parent.params) for (var i = 0; i < parent.params.length; i++)
if (parent.params[i].name == sub.name) target = parent.params[i]
if (!target) raise("Unknown parameter " + sub.name, sub.data.loc)
} else if (parent.type == "class" || parent.type == "interface" || parent.type == "Object") {
var path = splitPath(sub.name), target = parent
for (var i = 0; i < path.length; i++) {
var isStatic = i == path.length - 1 && sub.data.$static
target = deref(deref(target, isStatic ? "staticProperties" : "properties"), path[i])
}
} else {
raise("Can not add sub-fields to named type " + parent.type, sub.data.loc)
}
var stored = extend(sub.data, target, [sub.name], true)
sub.subcomments.forEach(function(sub) { applySubComment(stored, sub) })
}
VariableDeclarator: function(node) { return new Pos(items, node.id.name) },
function getPath(ancestors) {
var top = ancestors[ancestors.length - 1]
return top ? findPath[top.type](top, ancestors) : []
}
FunctionDeclaration: function(node) { return new Pos(items, node.id.name) },
var findPath = {
// FIXME destructuring
VariableDeclaration: function(node) { return [node.declarations[0].id.name] },
ClassDeclaration: function(node) { return new Pos(items, node.id.name) },
VariableDeclarator: function(node) { return [node.id.name] },
AssignmentExpression: function(node, ancestors) {
return lvalPos(items, node.left, ancestors)
},
FunctionDeclaration: function(node) { return [node.id.name] },
Property: function(node, ancestors) {
return new Pos(deref(findParent(items, ancestors), "properties"), propName(node, true))
},
ClassDeclaration: function(node) { return [node.id.name] },
MethodDefinition: function(node, ancestors) {
var parent = findParent(items, ancestors)
if (node.kind == "constructor")
return new Pos(parent, "constructor")
else
return new Pos(deref(parent, node.static ? "properties" : "instanceProperties"), propName(node, true))
},
AssignmentExpression: function(node, ancestors) {
return lvalPath(node.left, ancestors)
},
ExportNamedDeclaration: function(node, ancestors) {
return this[node.declaration.type](node.declaration, ancestors)
},
Property: function(node, ancestors) {
var path = parentPath(ancestors)
path.push(propName(node, true))
return path
},
ExportDefaultDeclaration: function() {
return new Pos(items, "default")
MethodDefinition: function(node, ancestors) {
var path = parentPath(ancestors)
if (node.kind == "constructor") {
path.push("#constructor")
} else {
if (!node.static) path.push("prototype")
path.push(propName(node, true))
}
return path
},
ExportNamedDeclaration: function(node, ancestors) {
return this[node.declaration.type](node.declaration, ancestors)
},
ExportDefaultDeclaration: function() {
return ["default"]
}
}
function addNameToPath(name, path, isStatic) {
var parts = splitPath(name)
for (var i = 0; i < parts.length; i++) {
if (path.length && ctorName(path[path.length - 1]) && (!isStatic || i < parts.length - 1))
path.push("prototype")
path.push(parts[i])
}
return path
}
function addData(top, path, data) {
var target = top, isCtor = false
for (var i = 0; i < path.length; i++) {
var cur = path[i], descend = "properties"
if (cur == "#constructor") {
target = deref(target, "constructor")
break
}
if (isCtor) {
if (cur == "prototype") {
if (i == path.length - 1) raise("Can not annotate constructor prototype", data.loc)
cur = path[++i]
} else {
descend = "staticProperties"
}
}
target = deref(deref(target, descend), cur)
isCtor = target.type ? target.type == "class" : ctorName(cur)
}
return extend(data, target, path)
}
var inferForNode = {
VariableDeclaration: function(node, data) {
var decl0 = node.declarations[0]
return inferExpr(decl0.init, data, node.kind, decl0.id.name)
return inferExpr(decl0.init, data, decl0.id.name)
},
VariableDeclarator: function(node, data, ancestors) {
var kind = ancestors[ancestors.length - 2].kind
return inferExpr(node.init, data, kind, node.id.name)
VariableDeclarator: function(node, data) {
return inferExpr(node.init, data, node.id.name)
},
FunctionDeclaration: function(node, data) {
return inferFn(node, data, "function", node.id.name)
return inferFn(node, data, node.id.name)
},
ClassDeclaration: function(node, data) {
return inferClass(node, data)
},
ClassDeclaration: inferClass,
ClassExpression: inferClass,
AssignmentExpression: function(node, data) {
return inferExpr(node.right, data, null, propName(node.left))
return inferExpr(node.right, data, propName(node.left))
},
Property: function(node, data) {
return inferExpr(node.value, data, null, propName(node, true))
return inferExpr(node.value, data, propName(node, true))
},
MethodDefinition: function(node, data) {
var kind = node.kind
if (kind == "get") kind = "getter"
else if (kind == "set") kind = "setter"
return inferFn(node.value, data, kind)
return inferFn(node.value, data)
},

@@ -143,4 +193,4 @@

function raise(msg, node) {
throw new SyntaxError(msg + " at " + node.loc.source.name + ":" + node.loc.start.line)
function raise(msg, loc) {
throw new SyntaxError(msg + " at " + (loc.file || loc.source.name) + ":" + (loc.start ? loc.start.line : loc.line))
}

@@ -158,3 +208,3 @@

return "[Symbol." + key.property.name + "]"
if (force) raise("Expected static property", node)
if (force) raise("Expected static property", node.loc)
}

@@ -185,4 +235,3 @@

function inferFn(node, data, kind, name) {
if (kind) data.kind = kind
function inferFn(node, data, name) {
var inferredParams = node.params.map(inferParam)

@@ -202,4 +251,3 @@

if (ctorName(name)) {
data.kind = "constructor"
return {constructor: data, kind: "class", loc: data.loc}
return {constructor: data, type: "class", loc: data.loc}
} else {

@@ -211,3 +259,2 @@ return data

function inferClass(node, data) {
data.kind = "class"
if (node.superClass && node.superClass.type == "Identifier") {

@@ -218,12 +265,21 @@ var loc = node.superClass.loc

}
if (!data.type) data.type = "class"
return data
}
function inferExpr(node, data, kind, name) {
if (kind) data.kind = kind
function inferExpr(node, data, name) {
if (!node) return data
if (node.type == "ClassExpression")
if (node.type == "ClassExpression") {
inferClass(node, data)
else if (node.type == "FunctionExpression" || node.type == "ArrowFunctionExpression")
inferFn(node, data, "function", name)
} else if (node.type == "FunctionExpression" || node.type == "ArrowFunctionExpression") {
inferFn(node, data, name)
} else if (node.type == "Literal" && !data.type) {
if (typeof node.value == "number") data.type = "number"
else if (typeof node.value == "boolean") data.type = "bool"
else if (typeof node.value == "string") data.type = "string"
else if (node.value instanceof RegExp) data.type = "RegExp"
} else if (node.type == "NewExpression" && !data.type) {
if (node.callee.type == "Identifier" && ctorName(node.callee.name))
data.type = node.callee.name
}
return data

@@ -234,12 +290,10 @@ }

function Pos (parent, name) { this.parent = parent; this.name = name }
function extend(from, to, path) {
function extend(from, to, path, overrideLoc) {
for (var prop in from) {
if (!(prop in to)) {
if (!(prop in to) || (prop == "loc" && overrideLoc)) {
to[prop] = from[prop]
} else if (prop == "properties" || prop == "instanceProperties") {
extend(from[prop], to[prop], path + "." + prop)
} else if (prop == "properties" || prop == "staticProperties") {
extend(from[prop], to[prop], path.concat(prop))
} else {
var msg = "Conflicting information for " + path + "." + prop
var msg = "Conflicting information for " + path.join(".") + "." + prop
if (to.loc) msg += " at " + to.loc.file + ":" + to.loc.line

@@ -253,19 +307,23 @@ if (from.loc) msg += (to.loc ? " and " : " at ") + from.loc.file + ":" + from.loc.line

Pos.prototype.add = function(data) {
var known = this.parent[this.name]
return known ? extend(data, known, this.name) : this.parent[this.name] = data
function deref(obj, name) {
return obj[name] || (obj[name] = Object.create(null))
}
Pos.prototype.deref = function() {
return this.parent[this.name] || (this.parent[this.name] = Object.create(null))
function assignIds(obj, path) {
if (path) obj.id = path
if (Object.prototype.hasOwnProperty.call(obj, "constructor"))
assignIds(obj.constructor, path + ".constructor")
if (obj.properties) for (var prop in obj.properties)
assignIds(obj.properties[prop], (path ? path + "." : "") + prop)
if (obj.staticProperties) for (var prop in obj.staticProperties)
assignIds(obj.staticProperties[prop], path + "^" + prop)
if (obj.params) for (var i = 0; i < obj.params.length; i++)
if (obj.params[i].name) assignIds(obj.params[i], path + "^" + obj.params[i].name)
if (obj.returns) assignIds(obj.returns, path + "^returns")
}
function deref(obj, name) {
return obj[name] || (obj[name] = Object.create(null))
}
function lvalPos(items, lval, ancestors) {
var path = [], target, name, inst = false
function lvalPath(lval, ancestors) {
var path = []
while (lval.type == "MemberExpression") {
path.push(propName(lval))
path.unshift(propName(lval))
lval = lval.object

@@ -275,34 +333,31 @@ }

if (lval.type == "Identifier") {
if (!path.length) return new Pos(items, lval.name)
target = deref(items, lval.name)
path.unshift(lval.name)
} else if (lval.type == "ThisExpression") {
target = findSelf(items, ancestors.slice(0, ancestors.length - 1))
inst = true
path = selfPath(ancestors.slice(0, ancestors.length - 1)).concat(path)
} else {
raise("Could not derive a target for this assignment", lval)
raise("Could not derive a target for this assignment", lval.loc)
}
for (var i = path.length - 1; i >= 0; i--) {
var name = path[i], descend = inst ? "instanceProperties" : "properties"
if (name == "prototype" && i) {
name = path[--i]
descend = "instanceProperties"
}
target = deref(target, descend)
if (i) target = deref(target, name)
inst = false
}
return new Pos(target, path[0])
return path
}
function findAssigned(items, ancestors) {
function assignedPath(ancestors) {
var top = ancestors[ancestors.length - 1]
if (top.type == "VariableDeclarator" && top.id.type == "Identifier")
return deref(items, top.id.name)
return [top.id.name]
else if (top.type == "AssignmentExpression")
return lvalPos(items, top.left, ancestors).deref()
return lvalPath(top.left, ancestors)
else
raise("Could not derive a name", top)
raise("Could not derive a name", top.loc)
}
function findPrototype(ancestors) {
var assign = ancestors[ancestors.length - 1]
if (assign.type != "AssignmentExpression") return null
var lval = assign.left
if (lval.type == "MemberExpression" && !lval.computed &&
lval.object.type == "MemberExpression" && !lval.object.computed &&
lval.object.property.name == "prototype")
return lvalPath(lval.object, ancestors)
}
function assignedName(node) {

@@ -315,72 +370,33 @@ if (node.type == "VariableDeclarator" && node.id.type == "Identifier")

function findPrototype(items, ancestors) {
var assign = ancestors[ancestors.length - 1]
if (assign.type != "AssignmentExpression") return null
for (var i = 0, lval = assign.left; i < 2; i++) {
if (lval.type != "MemberExpression" || lval.computed) return null
if (lval.property.name == "prototype")
return lvalPos(items, lval.object, ancestors).deref()
lval = lval.object
}
}
function findSelf(items, ancestors) {
function selfPath(ancestors) {
for (var i = ancestors.length - 1; i >= 0; i--) {
var forward = i && ancestors[i - 1].forward
if (forward) return posFromPath(items, forward).deref()
var ancestor = ancestors[i], found
if (ancestor.type == "ClassDeclaration")
return deref(items, ancestor.id.name)
else if (ancestor.type == "FunctionDeclaration" && ctorName(ancestor.id.name))
return deref(items, ancestor.id.name)
if (ancestor.type == "ClassDeclaration" ||
(ancestor.type == "FunctionDeclaration" && ctorName(ancestor.id.name)))
return [ancestor.id.name, "prototype"]
else if (i && (ancestor.type == "ClassExpression" ||
ancestor.type == "FunctionExpression" && ctorName(assignedName(ancestors[i - 1]))))
return findAssigned(items, ancestors.slice(0, i))
else if (i && /Function/.test(ancestor.type) &&
(found = findPrototype(items, ancestors.slice(0, i))))
return assignedPath(ancestors.slice(0, i)).concat("prototype")
else if (i && /Function/.test(ancestor.type) && (found = findPrototype(ancestors.slice(0, i))))
return found
}
raise("No context found for 'this'", ancestors[ancestors.length - 1])
raise("No context found for 'this'", ancestors[ancestors.length - 1].loc)
}
function findParent(items, ancestors) {
function parentPath(ancestors) {
for (var i = ancestors.length - 1; i >= 0; i--) {
var forward = i && ancestors[i - 1].forward
if (forward) return posFromPath(items, forward).deref()
var ancestor = ancestors[i]
if (ancestor.type == "ClassDeclaration")
return deref(items, ancestor.id.name)
return [ancestor.id.name]
else if (i && (ancestor.type == "ClassExpression" || ancestor.type == "ObjectExpression"))
return findAssigned(items, ancestors.slice(0, i))
return assignedPath(ancestors.slice(0, i))
}
}
function posFromPath(items, path) {
var target = items, next = path[0]
for (var i = 0; i < path.length - 1; i++) {
var name = next, descend = "properties"
next = path[i + 1]
if (i < path.length - 2 && next == "prototype") {
descend = "instanceProperties"
i++
next = path[i + 1]
} else if (next[0] == "#") {
descend = null
next = next.slice(1)
}
target = deref(target, name)
if (descend) target = deref(target, descend)
}
return new Pos(target, next)
}
function splitPath(path) {
var m, parts = [], rest = path, pound = ""
while (rest && (m = /^(\[.*?\]|[^\s\.#]+)(\.|#)?/.exec(rest))) {
parts.push(pound + m[1])
var m, parts = [], rest = path
while (rest && (m = /^(\[.*?\]|[^\s\.#]+)(\.)?/.exec(rest))) {
parts.push(m[1])
rest = rest.slice(m[0].length)
if (!m[2]) break
pound = m[2] == "#" ? "#" : ""
}

@@ -387,0 +403,0 @@ if (rest) throw new Error("Invalid path: " + path)

@@ -77,6 +77,6 @@ module.exports = function(string, start, loc) {

type.type = "Array"
type.content = [parse(input)]
type.typeParams = [parse(input)]
while (!input.eat("]")) {
if (!input.eat(",")) input.error("Missing comma or closing square bracket")
type.content.push(parse(input))
type.typeParams.push(parse(input))
}

@@ -97,6 +97,6 @@ } else if (input.eat("{")) {

if (input.eat("<")) {
type.content = []
type.typeParams = []
while (!input.eat(">")) {
if (type.content.length && !input.eat(",")) input.error("Missing comma or closing angle bracket")
type.content.push(parse(input))
if (type.typeParams.length && !input.eat(",")) input.error("Missing comma or closing angle bracket")
type.typeParams.push(parse(input))
}

@@ -103,0 +103,0 @@ }

@@ -1,2 +0,2 @@

// ;; The Foo class
// ::- The Foo class
class Foo {}

@@ -3,0 +3,0 @@

{
"Foo": {
"id": "Foo",
"description": "The Foo class",
"type": "class",
"loc": {

@@ -9,5 +11,5 @@ "file": "test/class_addmethod.js",

},
"kind": "class",
"instanceProperties": {
"properties": {
"bar": {
"id": "Foo.bar",
"type": "Function",

@@ -18,2 +20,3 @@ "params": [

"name": "a",
"id": "Foo.bar^a",
"loc": {

@@ -28,2 +31,3 @@ "file": "test/class_addmethod.js",

"name": "b",
"id": "Foo.bar^b",
"loc": {

@@ -38,2 +42,3 @@ "file": "test/class_addmethod.js",

"type": "bool",
"id": "Foo.bar^returns",
"loc": {

@@ -50,4 +55,3 @@ "file": "test/class_addmethod.js",

"column": 0
},
"kind": "function"
}
}

@@ -54,0 +58,0 @@ }

{
"Point": {
"id": "Point",
"constructor": {
"id": "Point.constructor",
"type": "Function",

@@ -9,2 +11,3 @@ "params": [

"name": "x",
"id": "Point.constructor^x",
"loc": {

@@ -19,2 +22,3 @@ "file": "test/class_ctor.js",

"name": "y",
"id": "Point.constructor^y",
"loc": {

@@ -32,6 +36,5 @@ "file": "test/class_ctor.js",

},
"description": "A vector type",
"kind": "constructor"
"description": "A vector type"
},
"kind": "class",
"type": "class",
"loc": {

@@ -42,4 +45,5 @@ "file": "test/class_ctor.js",

},
"instanceProperties": {
"properties": {
"x": {
"id": "Point.x",
"type": "number",

@@ -54,2 +58,3 @@ "loc": {

"y": {
"id": "Point.y",
"type": "number",

@@ -56,0 +61,0 @@ "loc": {

@@ -1,2 +0,2 @@

// ;; The Foo class
// ::- The Foo class
const Foo = class extends Bar {

@@ -3,0 +3,0 @@ // :: (number, string) -> bool

{
"Foo": {
"id": "Foo",
"description": "The Foo class",
"type": "class",
"loc": {

@@ -9,5 +11,5 @@ "file": "test/class_expr.js",

},
"kind": "class",
"instanceProperties": {
"properties": {
"m": {
"id": "Foo.m",
"type": "Function",

@@ -18,2 +20,3 @@ "params": [

"name": "a",
"id": "Foo.m^a",
"loc": {

@@ -28,2 +31,3 @@ "file": "test/class_expr.js",

"name": "b",
"id": "Foo.m^b",
"loc": {

@@ -38,2 +42,3 @@ "file": "test/class_expr.js",

"type": "bool",
"id": "Foo.m^returns",
"loc": {

@@ -48,4 +53,3 @@ "file": "test/class_expr.js",

"column": 2
},
"kind": "method"
}
}

@@ -52,0 +56,0 @@ },

@@ -1,2 +0,2 @@

// ;; The Foo class
// ::- The Foo class
class Foo extends Bar {

@@ -3,0 +3,0 @@ // :: (number, string) -> bool

{
"Foo": {
"id": "Foo",
"description": "The Foo class",
"type": "class",
"loc": {

@@ -9,3 +11,2 @@ "file": "test/class_static.js",

},
"kind": "class",
"extends": {

@@ -19,4 +20,5 @@ "type": "Bar",

},
"properties": {
"staticProperties": {
"m": {
"id": "Foo^m",
"type": "Function",

@@ -27,2 +29,3 @@ "params": [

"name": "a",
"id": "Foo^m^a",
"loc": {

@@ -37,2 +40,3 @@ "file": "test/class_static.js",

"name": "b",
"id": "Foo^m^b",
"loc": {

@@ -47,2 +51,3 @@ "file": "test/class_static.js",

"type": "bool",
"id": "Foo^m^returns",
"loc": {

@@ -58,4 +63,3 @@ "file": "test/class_static.js",

"column": 2
},
"kind": "method"
}
}

@@ -62,0 +66,0 @@ }

@@ -1,2 +0,2 @@

// ;; A Foo
// ::- A Foo
class Foo {

@@ -3,0 +3,0 @@ // :: (number)

{
"Foo": {
"id": "Foo",
"loc": {

@@ -9,4 +10,5 @@ "file": "test/class_this.js",

"description": "A Foo",
"kind": "class",
"type": "class",
"constructor": {
"id": "Foo.constructor",
"type": "Function",

@@ -17,2 +19,3 @@ "params": [

"name": "a",
"id": "Foo.constructor^a",
"loc": {

@@ -29,7 +32,7 @@ "file": "test/class_this.js",

"column": 2
},
"kind": "constructor"
}
},
"instanceProperties": {
"properties": {
"a": {
"id": "Foo.a",
"type": "number",

@@ -44,2 +47,3 @@ "loc": {

"b": {
"id": "Foo.b",
"type": "bool",

@@ -54,2 +58,3 @@ "loc": {

"doStuff": {
"id": "Foo.doStuff",
"type": "Function",

@@ -61,4 +66,3 @@ "params": [],

"column": 0
},
"kind": "function"
}
}

@@ -65,0 +69,0 @@ }

{
"foo": {
"id": "foo",
"type": "Function",

@@ -10,2 +11,3 @@ "params": [

"name": "arg",
"id": "foo^arg",
"loc": {

@@ -22,5 +24,4 @@ "file": "test/defaultarg.js",

"column": 0
},
"kind": "function"
}
}
}
{
"x": {
"id": "x",
"type": "Function",

@@ -7,2 +8,3 @@ "params": [

"name": "foo",
"id": "x^foo",
"type": "number",

@@ -17,2 +19,3 @@ "loc": {

"name": "bar",
"id": "x^bar",
"type": "string",

@@ -30,5 +33,4 @@ "loc": {

"column": 0
},
"kind": "function"
}
}
}
{
"hello": {
"id": "hello",
"type": "Function",

@@ -7,2 +8,3 @@ "params": [],

"type": "string",
"id": "hello^returns",
"loc": {

@@ -19,6 +21,6 @@ "file": "test/exported.js",

},
"kind": "function",
"exported": true
},
"x": {
"id": "x",
"type": "number",

@@ -30,6 +32,6 @@ "loc": {

},
"kind": "var",
"exported": true
},
"default": {
"id": "default",
"type": "string",

@@ -36,0 +38,0 @@ "loc": {

{
"add": {
"id": "add",
"type": "Function",

@@ -8,2 +9,3 @@ "params": [

"name": "a",
"id": "add^a",
"loc": {

@@ -18,2 +20,3 @@ "file": "test/function.js",

"name": "b",
"id": "add^b",
"loc": {

@@ -28,2 +31,3 @@ "file": "test/function.js",

"type": "number",
"id": "add^returns",
"loc": {

@@ -40,5 +44,4 @@ "file": "test/function.js",

"column": 0
},
"kind": "function"
}
}
}
{
"add": {
"id": "add",
"loc": {

@@ -17,3 +18,3 @@ "line": 1,

"type": "union",
"content": [
"typeParams": [
{

@@ -36,3 +37,4 @@ "loc": {

],
"name": "which"
"name": "which",
"id": "add^which"
},

@@ -46,3 +48,4 @@ {

"type": "number",
"name": "a"
"name": "a",
"id": "add^a"
},

@@ -56,3 +59,4 @@ {

"type": "number",
"name": "b"
"name": "b",
"id": "add^b"
}

@@ -66,7 +70,7 @@ ],

},
"type": "number"
"type": "number",
"id": "add^returns"
},
"description": "Returns a or b",
"kind": "function"
"description": "Returns a or b"
}
}
// :: {extra: number}
let obj = {
// ;; A property
// :: number A property
foo: 10,

@@ -8,3 +8,3 @@ bar: 20

// ;; An added property
// ::- An added property
obj.baz = 30
{
"obj": {
"id": "obj",
"type": "Object",
"properties": {
"extra": {
"id": "obj.extra",
"type": "number",

@@ -14,3 +16,5 @@ "loc": {

"foo": {
"id": "obj.foo",
"description": "A property",
"type": "number",
"loc": {

@@ -23,3 +27,5 @@ "file": "test/obj.js",

"baz": {
"id": "obj.baz",
"description": "An added property",
"type": "number",
"loc": {

@@ -36,5 +42,4 @@ "file": "test/obj.js",

"column": 0
},
"kind": "let"
}
}
}

@@ -1,10 +0,11 @@

// ;; #kind=class #path=Abc
// Abc:: class
// The Abc class
// :: (a: number) → number
// #path=Abc.prototype.m1
// Abc.m1:: (a: number) → number
// Method #1
// :: string
// #path=myString
// Abc.m2:: () #static
// Method #2
// myString:: string
// My string
{
"Abc": {
"id": "Abc",
"loc": {

@@ -8,8 +9,7 @@ "line": 1,

},
"kind": "class",
"$path": "Abc",
"description": "The Abc class",
"instanceProperties": {
"type": "class",
"properties": {
"m1": {
"kind": "method",
"id": "Abc.m1",
"loc": {

@@ -29,3 +29,4 @@ "line": 4,

"type": "number",
"name": "a"
"name": "a",
"id": "Abc.m1^a"
}

@@ -39,13 +40,27 @@ ],

},
"type": "number"
"type": "number",
"id": "Abc.m1^returns"
},
"$path": "Abc.prototype.m1",
"description": "Method #1"
}
},
"staticProperties": {
"m2": {
"id": "Abc^m2",
"loc": {
"line": 7,
"column": 0,
"file": "test/phantom.js"
},
"type": "Function",
"params": [],
"$static": "true",
"description": "Method #2"
}
}
},
"myString": {
"kind": "const",
"id": "myString",
"loc": {
"line": 8,
"line": 10,
"column": 0,

@@ -55,5 +70,4 @@ "file": "test/phantom.js"

"type": "string",
"$path": "myString",
"description": "My string"
}
}
{
"bar": {
"id": "bar",
"type": "Function",

@@ -7,3 +8,3 @@ "params": [

"type": "Array",
"content": [{
"typeParams": [{
"type": "string",

@@ -18,2 +19,3 @@ "loc": {

"name": "stuff",
"id": "bar^stuff",
"loc": {

@@ -28,2 +30,3 @@ "file": "test/restarg.js",

"type": "bool",
"id": "bar^returns",
"loc": {

@@ -39,5 +42,4 @@ "file": "test/restarg.js",

"column": 0
},
"kind": "function"
}
}
}

@@ -13,3 +13,3 @@ var fs = require("fs")

var jsfile = "/" + isJSON[1] + ".js"
var returned = getdocs.gather(fs.readFileSync(__dirname + jsfile, "utf8"), "test" + jsfile)
var returned = getdocs.gather(fs.readFileSync(__dirname + jsfile, "utf8"), {filename: "test" + jsfile})
try {

@@ -29,3 +29,3 @@ compare(returned, expected, "")

if (typeof a != "object" || typeof b != "object") {
if (a !== b) throw new Error("Mismatch at " + path)
if (a !== b) throw new Error("Mismatch at " + path + ": " + a + " vs " + b)
} else {

@@ -32,0 +32,0 @@ for (var prop in a) if (hop(a, prop)) {

@@ -1,3 +0,3 @@

// ;; #deprecated #exported=false #context="a \"string\""
// :: () #deprecated #exported=false #context="a \"string\""
// Hello!
function foo() {}
{
"foo": {
"id": "foo",
"loc": {

@@ -12,3 +13,2 @@ "file": "test/tags.js",

"description": "Hello!",
"kind": "function",
"type": "Function",

@@ -15,0 +15,0 @@ "params": []

{
"blah": {
"id": "blah",
"type": "Function",

@@ -7,3 +8,3 @@ "params": [

"type": "union",
"content": [
"typeParams": [
{

@@ -27,2 +28,3 @@ "type": "number",

"name": "x",
"id": "blah^x",
"loc": {

@@ -36,3 +38,3 @@ "file": "test/union.js",

"type": "union",
"content": [
"typeParams": [
{

@@ -61,3 +63,3 @@ "type": "bool",

},
"content": [{
"typeParams": [{
"type": "Error",

@@ -74,2 +76,3 @@ "loc": {

"name": "y",
"id": "blah^y",
"loc": {

@@ -84,3 +87,4 @@ "file": "test/union.js",

"type": "union",
"content": [
"id": "blah^returns",
"typeParams": [
{

@@ -114,5 +118,4 @@ "type": "number",

},
"description": "That's one complex signature",
"kind": "function"
"description": "That's one complex signature"
}
}