Comparing version
@@ -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", |
144
README.md
@@ -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 | ||
} |
360
src/index.js
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" | ||
} | ||
} |
55434
16.47%49
8.89%1818
15.36%236
22.92%