Full Code of marijnh/getdocs for AI

master 7b8a575b3f1f cached
49 files
54.7 KB
16.5k tokens
54 symbols
1 requests
Download .txt
Repository: marijnh/getdocs
Branch: master
Commit: 7b8a575b3f1f
Files: 49
Total size: 54.7 KB

Directory structure:
gitextract__rpuop8z/

├── .gitignore
├── LICENSE
├── README.md
├── bin/
│   └── getdocs.js
├── package.json
├── src/
│   ├── doccomments.js
│   ├── index.js
│   └── parsetype.js
└── test/
    ├── class_addmethod.js
    ├── class_addmethod.json
    ├── class_ctor.js
    ├── class_ctor.json
    ├── class_expr.js
    ├── class_expr.json
    ├── class_simple.js
    ├── class_simple.json
    ├── class_static.js
    ├── class_static.json
    ├── class_this.js
    ├── class_this.json
    ├── defaultarg.js
    ├── defaultarg.json
    ├── diffargname.js
    ├── diffargname.json
    ├── exported.js
    ├── exported.json
    ├── function.js
    ├── function.json
    ├── functionsub.js
    ├── functionsub.json
    ├── infer.js
    ├── infer.json
    ├── literal.js
    ├── literal.json
    ├── namedprop.js
    ├── namedprop.json
    ├── obj.js
    ├── obj.json
    ├── phantom.js
    ├── phantom.json
    ├── restarg.js
    ├── restarg.json
    ├── run.js
    ├── subcomment.js
    ├── subcomment.json
    ├── tags.js
    ├── tags.json
    ├── union.js
    └── union.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/node_modules
.tern-*


================================================
FILE: LICENSE
================================================
Copyright (C) 2013 by Marijn Haverbeke <marijn@haverbeke.berlin> and others

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# Getdocs

Getdocs is like JSDoc or documentation.js, running over ES6 code to
extract information and inline documentation in order to generate
docs, but without all the @s. It takes source files and outputs JSON.

For example, if you have this file, `foo.js`:

```javascript
// :: (number, number) → number
// Add two numbers
export function plus(a, b = 2) {
  return a + b
}
```

You can say `getdocs foo.js` to get this JSON:

```json
{
  "plus": {
    "type": "Function",
    "params": [
      {
        "type": "number",
        "name": "a"
      },
      {
        "type": "number",
        "default": "2",
        "optional": true,
        "name": "b"
      }
    ],
    "returns": { "type": "number" },
    "description": "Add two numbers",
    "exported": true
  }
}
```

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 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.

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.

The documented items found in the files passed to getdocs will be
returned as part of a big JSON object. Nesting is only applied for
class and object properties, where the properties are moved under the
`properties` object of the item they are part of. _A single namespace
is assumed for the documented identifiers in the group of files._

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

A type can be:

 * A JavaScript identifier, optionally followed by any number of
   properties, which are a dot character followed by a JavaScript
   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).

 * An array type, which is a type wrapped in `[` and `]`. `[x]` is
   equivalent to `Array<x>`.

 * A function type, which is written as a parenthesized list of
   argument types. Each argument type may optionally be prefixed with
   an argument name, which is an identifier followed by a colon. When
   an argument is prefixed by the string `...`, it is marked as a
   `rest` argument. After the closing parenthesis, an optional return
   type may appear after an arrow, written either `→` or `->`.

 * A nullable type, written as a question mark followed by a type.

 * An unspecified or “any” type, written as an asterisk `*`.

 * An object type, written as a list of properties wrapped in `{` and
   `}` braces. Each property must start with an identifier, followed
   by a colon, followed by a type.

 * A string literal, enclosed by double quotes, or a number literal.

 * A type followed by `extends` followed by another type, to indicate
   a sub-type.

Here are some examples of types:

 * `Math.pow`: `(base: number, exponent: number) → number`

 * `Element.insertBefore`: `(newNode: Node, before: ?Node) → Node`

 * `console.log`: `(...data: *)`

 * A pair of coordinates: `{x: number, y: number}`

 * An array of strings: `[string]`

 * 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).

## Tags

It is possible to add tags to a documented item. These are words
prefixed with a `#` character, appearing at the start of the comment —
that is, immediately after the type.

A tag like `#deprecated`, for example, will result in a `$deprecated:
"true"` property on the given item. The property is named by
prepending the tag's name with a dollar sign.

You can give tags an explicit value other than `"true"` by writing an
`=` character followed either by a word (a sequence of characters
without whitespace) or a quoted JavaScript-style string. For example
`#chapter=selection` or `#added="2.1.0"`.

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).

## Output JSON

The returned object maps item names to item descriptions. The
following properties can appear in a description for a documented
item:

 * **description**: The doc comment for the item.

 * **loc**: A `{line, column, file}` object pointing at the start of the item.

 * **exported**: Set if the item is exported using ES6 module syntax.

 * **constructor**: For classes with a documented constructor, this
   points at the constructor function.

 * **extends**: Holds the type of the supertype of a class or other
     sub-type.

 * **staticProperties**: For classes, this holds properties and
   methods that appear directly on the constructor.

In addition, they may have these properties, which can also appear on
nested types:

 * **type**: The name of the type. Instances of classes should use the
   (capitalized) class name. Builtin types will have names like
   `Array` or `Function`. Getdocs does not prescribe a naming of
   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
   types. Parameter types can have these additional properties:

     * **name**: The name of the parameter.

     * **rest**: Set when this is a rest parameter.

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

 * **returns**: For function types, this holds the type that is
   returned.

 * **typeParams**: For array types or named types with parameters
   (angle bracket syntax), this holds an array of parameter 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.


================================================
FILE: bin/getdocs.js
================================================
#!/usr/bin/env node
var fs = require("fs")
var glob = require("glob")
var getdocs = require("../src")

var items = {}

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

console.log(JSON.stringify(items, null, 2))


================================================
FILE: package.json
================================================
{
  "name": "getdocs",
  "version": "0.6.1",
  "description": "An extractor for succinct documentation comments in JavaScript code",
  "main": "src/index.js",
  "bin": {
    "getdocs": "bin/getdocs.js"
  },
  "scripts": {
    "test": "node test/run.js"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/marijnh/getdocs.git"
  },
  "keywords": [
    "documentation",
    "comment"
  ],
  "maintainers": [
    {
      "name": "Marijn Haverbeke",
      "email": "marijn@haverbeke.berlin",
      "web": "http://marijnhaverbeke.nl"
    }
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/marijnh/getdocs/issues"
  },
  "dependencies": {
    "acorn": "^2.6.0",
    "glob": "^6.0.1"
  }
}


================================================
FILE: src/doccomments.js
================================================
var acorn = require("acorn/dist/acorn")
var walk = require("acorn/dist/walk")

var parseType = require("./parsetype")

function strip(lines) {
  for (var head, i = 1; i < lines.length; i++) {
    var line = lines[i], lineHead = line.match(/^[\s\*]*/)[0]
    if (lineHead != line) {
      if (head == null) {
        head = lineHead
      } else {
        var same = 0
        while (same < head.length && head.charCodeAt(same) == lineHead.charCodeAt(same)) ++same
        if (same < head.length) head = head.slice(0, same)
      }
    }
  }
  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)
  }

  outer: for (var i = 0; i < lines.length; i++) {
    var line = lines[i].replace(/\s+$/, "")
    if (i == 0 && head != null) {
      for (var j = 0; j < head.length; j++) {
        var found = line.indexOf(head.slice(j))
        if (found == 0) {
          lines[i] = line.slice(head.length - j)
          continue outer
        }
      }
    }
    if (head == null || i == 0)
      lines[i] = line.replace(/^[\s\*]*/, "")
    else if (line.length < head.length)
      lines[i] = ""
    else
      lines[i] = line.slice(head.length)
  }

  while (lines.length && !lines[lines.length - 1]) lines.pop()
  while (lines.length && !lines[0]) lines.shift()
  return lines.join("\n")
}

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, {
    ecmaVersion: 6,
    locations: true,
    sourceFile: {text: text, name: filename},
    sourceType: "module",
    onComment: function(block, text, start, end, startLoc, endLoc) {
      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 {
        current = null
      }
      if (options.onComment) options.onComment(block, text, start, end, startLoc, endLoc)
    }
  })

  for (var i = 0; i < found.length; i++) {
    var comment = found[i], loc = comment.startLoc
    loc.file = filename
    comment.parsed = parseNestedComments(strip(comment.text), comment.startLoc)
  }
  return {ast: ast, comments: found}
}

function Found() {}

exports.findNodeAfter = function(ast, pos, types) {
  var stack = []
  function c(node, _, override) {
    if (node.end < pos) return
    if (node.start >= pos && types[node.type]) {
      stack.push(node)
      throw new Found
    }
    if (!override) stack.push(node)
    walk.base[override || node.type](node, null, c)
    if (!override) stack.pop()
  }
  try {
    c(ast)
  } catch (e) {
    if (e instanceof Found) return stack
    throw e
  }
}

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*([\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(end)
  while (match = /^\s*#([\w$]+)(?:=([^"]\S*|"(?:[^"\\]|\\.)*"))?\s*/.exec(text)) {
    text = text.slice(match[0].length)
    var value = match[2] || "true"
    if (value.charAt(0) == '"') value = JSON.parse(value)
    data["$" + match[1]] = value
  }

  if (/\S/.test(text)) data.description = text
  return {data: data, name: name, subcomments: []}
}

function dropIndent(text) {
  var lines = text.split("\n"), indent = 1e7
  for (var i = 0; i < lines.length; i++)
    if (/\S/.test(lines[i]))
      indent = Math.min(indent, /^\s*/.exec(lines[i])[0].length)
  if (indent == 0) return text
  for (var i = 0; i < lines.length; i++) lines[i] = lines[i].slice(indent)
  return lines.join("\n")
}

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(dropIndent(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
}


================================================
FILE: src/index.js
================================================
var docComments = require("./doccomments")
exports.stripComment = docComments.stripComment
var parseType = exports.parseType = require("./parsetype")

exports.gather = function(text, options) {
  var items = options.items || Object.create(null)
  var top = {properties: items}

  var found = docComments.parse(text, options)

  found.comments.forEach(function(comment) {
    var data = comment.parsed.data

    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 stored = addData(top, path, data)

    comment.parsed.subcomments.forEach(function(sub) { applySubComment(stored, sub) })
  })

  // Mark locals exported with `export {a, b, c}` statements as exported
  for (var i = 0; i < found.ast.body.length; i++) {
    var node = found.ast.body[i]
    if (node.type == "ExportNamedDeclaration" && !node.source) {
      for (var j = 0; j < node.specifiers.length; j++) {
        var spec = node.specifiers[j]
        var known = items[spec.local.name]
        if (known) known.exported = true
      }
    }
  }

  assignIds(top)

  return items
}

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) })
}

function getPath(ancestors) {
  var top = ancestors[ancestors.length - 1]
  return top ? findPath[top.type](top, ancestors) : []
}

var findPath = {
  // FIXME destructuring
  VariableDeclaration: function(node) { return [node.declarations[0].id.name] },

  VariableDeclarator: function(node) { return [node.id.name] },

  FunctionDeclaration: function(node) { return [node.id.name] },

  ClassDeclaration: function(node) { return [node.id.name] },

  AssignmentExpression: function(node, ancestors) {
    return lvalPath(node.left, ancestors)
  },

  Property: function(node, ancestors) {
    var path = parentPath(ancestors)
    path.push(propName(node, true))
    return path
  },

  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, decl0.id.name)
  },

  VariableDeclarator: function(node, data) {
    return inferExpr(node.init, data, node.id.name)
  },

  FunctionDeclaration: function(node, data) {
    return inferFn(node, data, node.id.name)
  },

  ClassDeclaration: inferClass,
  ClassExpression: inferClass,

  AssignmentExpression: function(node, data) {
    return inferExpr(node.right, data, propName(node.left))
  },

  Property: function(node, data) {
    return inferExpr(node.value, data, propName(node, true))
  },

  MethodDefinition: function(node, data) {
    return inferFn(node.value, data)
  },

  ExportNamedDeclaration: function(node, data, ancestors) {
    var inner = this[node.declaration.type](node.declaration, data, ancestors)
    inner.exported = true
    return inner
  },

  ExportDefaultDeclaration: function(node, data, ancestors) {
    var decl = node.declaration
    if (this[decl.type])
      data = this[decl.type](decl, data, ancestors)
    else
      data = inferExpr(decl, data)
    data.exported = true
    return data
  }
}

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

function propName(node, force) {
  var key = node.key || node.property
  if (!node.computed && key.type == "Identifier") return key.name
  if (key.type == "Literal") {
    if (typeof key.value == "string") return key.value
    if (typeof key.value == "number") return String(key.value)
  }
  if (node.computed && key.type == "MemberExpression" &&
      !key.computed && key.object.name == "Symbol")
    return "[Symbol." + key.property.name + "]"
  if (force) raise("Expected static property", node.loc)
}

function inferParam(n) {
  var param = Object.create(null)
  param.type = "any"
  param.loc = n.loc.start
  param.loc.file = n.loc.source.name
  if (n.type == "RestElement") {
    param.rest = true
    n = n.argument
  }
  if (n.type == "AssignmentPattern") {
    if (n.right.end - n.right.start < 20)
      param.default = n.loc.source.text.slice(n.right.start, n.right.end)
    n = n.left
    param.optional = true
  }
  if (n.type == "Identifier") param.name = n.name
  return param
}

function ctorName(name) {
  return name && /^[A-Z]/.test(name)
}

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

  if (!data.type) {
    data.type = "Function"
    data.params = inferredParams
  } else if (data.type == "Function") {
    for (var i = 0, e = Math.min(data.params.length, node.params.length); i < e; i++) {
      var from = inferredParams[i], to = data.params[i]
      for (var prop in from) if (!(prop in to)) to[prop] = from[prop]
    }
  }
  if (node.generator) data.generator = true

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

function inferClass(node, data) {
  if (node.superClass && node.superClass.type == "Identifier") {
    var loc = node.superClass.loc
    loc.start.file = loc.source.name
    data.extends = parseType(node.superClass.name, 0, loc.start).type
  }
  if (!data.type) data.type = "class"
  return data
}

function inferExpr(node, data, name) {
  if (!node) return data
  if (node.type == "ClassExpression") {
    inferClass(node, data)
  } 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
}

// Deriving context from ancestor nodes

function extend(from, to, path, overrideLoc) {
  for (var prop in from) {
    if (!(prop in to) || (prop == "loc" && overrideLoc)) {
      to[prop] = from[prop]
    } else if (prop == "properties" || prop == "staticProperties") {
      extend(from[prop], to[prop], path.concat(prop))
    } else {
      var msg = "Conflicting information for " + path.join(".") + "." + prop
      if (to.loc) msg += " at " + to.loc.file + ":" + to.loc.line
      if (from.loc) msg += (to.loc ? " and " : " at ") + from.loc.file + ":" + from.loc.line
      throw new SyntaxError(msg)
    }
  }
  return to
}

function deref(obj, name) {
  return obj[name] || (obj[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 lvalPath(lval, ancestors) {
  var path = []
  while (lval.type == "MemberExpression") {
    path.unshift(propName(lval))
    lval = lval.object
  }

  if (lval.type == "Identifier") {
    path.unshift(lval.name)
  } else if (lval.type == "ThisExpression") {
    path = selfPath(ancestors.slice(0, ancestors.length - 1)).concat(path)
  } else {
    raise("Could not derive a target for this assignment", lval.loc)
  }
  return path
}

function assignedPath(ancestors) {
  var top = ancestors[ancestors.length - 1]
  if (top.type == "VariableDeclarator" && top.id.type == "Identifier")
    return [top.id.name]
  else if (top.type == "AssignmentExpression")
    return lvalPath(top.left, ancestors)
  else
    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) {
  if (node.type == "VariableDeclarator" && node.id.type == "Identifier")
    return node.id.name
  else if (node.type == "AssignmentExpression")
    return propName(node.left)
}

function selfPath(ancestors) {
  for (var i = ancestors.length - 1; i >= 0; i--) {
    var ancestor = ancestors[i], found
    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 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].loc)
}

function parentPath(ancestors) {
  for (var i = ancestors.length - 1; i >= 0; i--) {
    var ancestor = ancestors[i]
    if (ancestor.type == "ClassDeclaration")
      return [ancestor.id.name]
    else if (i && (ancestor.type == "ClassExpression" || ancestor.type == "ObjectExpression"))
      return assignedPath(ancestors.slice(0, i))
  }
}

function splitPath(path) {
  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
  }
  if (rest) throw new Error("Invalid path: " + path)
  return parts
}


================================================
FILE: src/parsetype.js
================================================
module.exports = function(string, start, loc) {
  var input = new Input(string, start, loc)
  var result = parse(input)
  input.skip()
  return {type: result, end: input.pos}
}

function isSpace(ch) {
  return (ch < 14 && ch > 8) || ch === 32 || ch === 160;
}

function Input(string, start, loc) {
  this.str = string
  this.pos = start
  this.loc = loc
  this.skip()
}

Input.prototype = {
  skip: function() {
    while (this.pos < this.str.length && isSpace(this.str.charCodeAt(this.pos))) ++this.pos
  },

  atEnd: function() {
    return this.pos == this.str.length
  },

  eat: function(ch) {
    if (this.str.charCodeAt(this.pos) == ch.charCodeAt(0)) {
      this.pos++
      this.skip()
      return true
    }
  },

  match: function(re) {
    var match = re.exec(this.str.slice(this.pos))
    if (match) {
      this.pos += match[0].length
      this.skip()
      return match
    }
  },

  error: function(message) {
    throw new SyntaxError(message + " for " + this.loc.file + ":" + this.loc.line)
  }
}

function parse(input) {
  var base = parseBase(input)
  if (input.match(/^extends\b/)) base.extends = parse(input)
  return base
}

function parseBase(input) {
  if (input.eat("?")) {
    var inner = parse(input)
    inner.optional = true
    return inner
  }
  var type = Object.create(null)
  type.loc = input.loc
  if (input.eat("*")) {
    type.type = "any"
  } else if (input.eat("(")) {
    type.type = "Function"
    type.params = []
    while (!input.eat(")")) {
      if (type.params.length && !input.eat(",")) input.error("Missing comma or closing paren")
      var rest = input.match(/^\.\.\./)
      var name = input.match(/^([\w$]+)(\??)\s*:/)
      var param = parse(input)
      if (rest) param.rest = true
      if (name) param.name = name[1]
      if (name && name[2]) param.optional = true
      type.params.push(param)
    }
    if (input.match(/^(?:→|->)/))
      type.returns = parse(input)
  } else if (input.eat("[")) {
    type.type = "Array"
    type.typeParams = [parse(input)]
    while (!input.eat("]")) {
      if (!input.eat(",")) input.error("Missing comma or closing square bracket")
      type.typeParams.push(parse(input))
    }
  } else if (input.eat("{")) {
    type.type = "Object"
    type.properties = Object.create(null)
    for (var first = true; !input.eat("}"); first = false) {
      if (!first && !input.eat(",")) input.error("Missing comma or closing brace")
      var name = input.match(/^([\w$]+)\s*:/)
      if (!name) input.error("Malformed object type")
      type.properties[name[1]] = parse(input)
    }
  } else {
    var name = input.match(/^(?:[\w$]+(?:\.[\w$]+)*|"(?:[^"]|\.)*")/)
    if (!name) input.error("Unexpected syntax: " + input.str.slice(input.pos, input.pos + 5))
    type.type = name[0]
    if (input.eat("<")) {
      type.typeParams = []
      while (!input.eat(">")) {
        if (type.typeParams.length && !input.eat(",")) input.error("Missing comma or closing angle bracket")
        type.typeParams.push(parse(input))
      }
    }
  }
  return type
}


================================================
FILE: test/class_addmethod.js
================================================
// ::- The Foo class
class Foo {}

// :: (number, string) → bool
// A method
Foo.prototype.bar = (a, b) => true


================================================
FILE: test/class_addmethod.json
================================================
{
  "Foo": {
    "id": "Foo",
    "description": "The Foo class",
    "type": "class",
    "loc": {
      "file": "test/class_addmethod.js",
      "line": 1,
      "column": 0
    },
    "properties": {
      "bar": {
        "id": "Foo.bar",
        "type": "Function",
        "params": [
          {
            "type": "number",
            "name": "a",
            "id": "Foo.bar^a",
            "loc": {
              "file": "test/class_addmethod.js",
              "line": 4,
              "column": 0
            }
          },
          {
            "type": "string",
            "name": "b",
            "id": "Foo.bar^b",
            "loc": {
              "file": "test/class_addmethod.js",
              "line": 4,
              "column": 0
            }
          }
        ],
        "returns": {
          "type": "bool",
          "id": "Foo.bar^returns",
          "loc": {
            "file": "test/class_addmethod.js",
            "line": 4,
            "column": 0
          }
        },
        "description": "A method",
        "loc": {
          "file": "test/class_addmethod.js",
          "line": 4,
          "column": 0
        }
      }
    }
  }
}


================================================
FILE: test/class_ctor.js
================================================
// :: (number, number) A vector type
function Point(x, y) {
  // :: number The x coordinate
  this.x = x
  // :: number The y coordinate
  this.y = y
}



================================================
FILE: test/class_ctor.json
================================================
{
  "Point": {
    "id": "Point",
    "constructor": {
      "id": "Point.constructor",
      "type": "Function",
      "params": [
        {
          "type": "number",
          "name": "x",
          "id": "Point.constructor^x",
          "loc": {
            "file": "test/class_ctor.js",
            "line": 1,
            "column": 0
          }
        },
        {
          "type": "number",
          "name": "y",
          "id": "Point.constructor^y",
          "loc": {
            "file": "test/class_ctor.js",
            "line": 1,
            "column": 0
          }
        }
      ],
      "loc": {
        "file": "test/class_ctor.js",
        "line": 1,
        "column": 0
      },
      "description": "A vector type"
    },
    "type": "class",
    "loc": {
      "file": "test/class_ctor.js",
      "line": 1,
      "column": 0
    },
    "properties": {
      "x": {
        "id": "Point.x",
        "type": "number",
        "loc": {
          "file": "test/class_ctor.js",
          "line": 3,
          "column": 2
        },
        "description": "The x coordinate"
      },
      "y": {
        "id": "Point.y",
        "type": "number",
        "loc": {
          "file": "test/class_ctor.js",
          "line": 5,
          "column": 2
        },
        "description": "The y coordinate"
      }
    }
  }
}


================================================
FILE: test/class_expr.js
================================================
// ::- The Foo class
const Foo = class extends Bar {
  // :: (number, string) -> bool
  m(a, b) { return true }
}


================================================
FILE: test/class_expr.json
================================================
{
  "Foo": {
    "id": "Foo",
    "description": "The Foo class",
    "type": "class",
    "loc": {
      "file": "test/class_expr.js",
      "line": 1,
      "column": 0
    },
    "properties": {
      "m": {
        "id": "Foo.m",
        "type": "Function",
        "params": [
          {
            "type": "number",
            "name": "a",
            "id": "Foo.m^a",
            "loc": {
              "file": "test/class_expr.js",
              "line": 3,
              "column": 2
            }
          },
          {
            "type": "string",
            "name": "b",
            "id": "Foo.m^b",
            "loc": {
              "file": "test/class_expr.js",
              "line": 3,
              "column": 2
            }
          }
        ],
        "returns": {
          "type": "bool",
          "id": "Foo.m^returns",
          "loc": {
            "file": "test/class_expr.js",
            "line": 3,
            "column": 2
          }        },
        "loc": {
          "file": "test/class_expr.js",
          "line": 3,
          "column": 2
        }
      }
    },
    "extends": {
      "type": "Bar",
      "loc": {
        "line": 2,
        "column": 26,
        "file": "test/class_expr.js"
      }
    }
  }
}


================================================
FILE: test/class_simple.js
================================================
// ::- The Foo class
class Foo extends Bar {
  // :: (number, number)
  constructor(a, b) {
    this.a = a
    this.b = b
  }

  // :: (number, string) -> bool
  m(a, b) { return true }
}


================================================
FILE: test/class_simple.json
================================================
{
  "Foo": {
    "id": "Foo",
    "description": "The Foo class",
    "loc": {
      "file": "test/class_simple.js",
      "line": 1,
      "column": 0
    },
    "type": "class",
    "properties": {
      "m": {
        "id": "Foo.m",
        "type": "Function",
        "params": [
          {
            "type": "number",
            "name": "a",
            "id": "Foo.m^a",
            "loc": {
              "file": "test/class_simple.js",
              "line": 9,
              "column": 2
            }
          },
          {
            "type": "string",
            "name": "b",
            "id": "Foo.m^b",
            "loc": {
              "file": "test/class_simple.js",
              "line": 9,
              "column": 2
            }
          }
        ],
        "returns": {
          "type": "bool",
          "id": "Foo.m^returns",
          "loc": {
            "file": "test/class_simple.js",
            "line": 9,
            "column": 2
          }
        },
        "loc": {
          "file": "test/class_simple.js",
          "line": 9,
          "column": 2
        }
      }
    },
    "extends": {
      "type": "Bar",
      "loc": {
        "line": 2,
        "column": 18,
        "file": "test/class_simple.js"
      }
    },
    "constructor": {
      "id": "Foo.constructor",
      "loc": {
        "file": "test/class_simple.js",
        "line": 3,
        "column": 2
      },
      "type": "Function",
      "params": [
        {
          "type": "number",
          "name": "a",
          "id": "Foo.constructor^a",
          "loc": {
            "file": "test/class_simple.js",
            "line": 3,
            "column": 2
          }
        },
        {
          "type": "number",
          "name": "b",
          "id": "Foo.constructor^b",
          "loc": {
            "file": "test/class_simple.js",
            "line": 3,
            "column": 2
          }
        }
      ]
    }
  }
}


================================================
FILE: test/class_static.js
================================================
// ::- The Foo class
class Foo extends Bar {
  // :: (number, string) -> bool
  static m(a, b) { return true }
}


================================================
FILE: test/class_static.json
================================================
{
  "Foo": {
    "id": "Foo",
    "description": "The Foo class",
    "type": "class",
    "loc": {
      "file": "test/class_static.js",
      "line": 1,
      "column": 0
    },
    "extends": {
      "type": "Bar",
      "loc": {
        "line": 2,
        "column": 18,
        "file": "test/class_static.js"
      }
    },
    "staticProperties": {
      "m": {
        "id": "Foo^m",
        "type": "Function",
        "params": [
          {
            "type": "number",
            "name": "a",
            "id": "Foo^m^a",
            "loc": {
              "file": "test/class_static.js",
              "line": 3,
              "column": 2
            }
          },
          {
            "type": "string",
            "name": "b",
            "id": "Foo^m^b",
            "loc": {
              "file": "test/class_static.js",
              "line": 3,
              "column": 2
            }
          }
        ],
        "returns": {
          "type": "bool",
          "id": "Foo^m^returns",
          "loc": {
            "file": "test/class_static.js",
            "line": 3,
            "column": 2
          }
        },
        "loc": {
          "file": "test/class_static.js",
          "line": 3,
          "column": 2
        }
      }
    }
  }
}


================================================
FILE: test/class_this.js
================================================
// ::- A Foo
class Foo {
  // :: (number)
  constructor(a) {
    // :: number
    // The a property
    this.a = a
  }
}

// :: ()
Foo.prototype.doStuff = function() {
  // :: bool
  // The b property
  this.b = true
}


================================================
FILE: test/class_this.json
================================================
{
  "Foo": {
    "id": "Foo",
    "loc": {
      "file": "test/class_this.js",
      "line": 1,
      "column": 0
    },
    "description": "A Foo",
    "type": "class",
    "constructor": {
      "id": "Foo.constructor",
      "type": "Function",
      "params": [
        {
          "type": "number",
          "name": "a",
          "id": "Foo.constructor^a",
          "loc": {
            "file": "test/class_this.js",
            "line": 3,
            "column": 2
          }
        }
      ],
      "loc": {
        "file": "test/class_this.js",
        "line": 3,
        "column": 2
      }
    },
    "properties": {
      "a": {
        "id": "Foo.a",
        "type": "number",
        "loc": {
          "file": "test/class_this.js",
          "line": 5,
          "column": 4
        },
        "description": "The a property"
      },
      "b": {
        "id": "Foo.b",
        "type": "bool",
        "loc": {
          "file": "test/class_this.js",
          "line": 13,
          "column": 2
        },
        "description": "The b property"
      },
      "doStuff": {
        "id": "Foo.doStuff",
        "type": "Function",
        "params": [],
        "loc": {
          "file": "test/class_this.js",
          "line": 11,
          "column": 0
        }
      }
    }
  }
}


================================================
FILE: test/defaultarg.js
================================================
// :: (bool)
function foo(arg = false) {}


================================================
FILE: test/defaultarg.json
================================================
{
  "foo": {
    "id": "foo",
    "type": "Function",
    "params": [
      {
        "type": "bool",
        "default": "false",
        "optional": true,
        "name": "arg",
        "id": "foo^arg",
        "loc": {
          "file": "test/defaultarg.js",
          "line": 1,
          "column": 0
        }
      }
    ],
    "loc": {
      "file": "test/defaultarg.js",
      "line": 1,
      "column": 0
    }
  }
}


================================================
FILE: test/diffargname.js
================================================
// :: (foo: number, bar: string)
function x(a, b) {}


================================================
FILE: test/diffargname.json
================================================
{
  "x": {
    "id": "x",
    "type": "Function",
    "params": [
      {
        "name": "foo",
        "id": "x^foo",
        "type": "number",
        "loc": {
          "file": "test/diffargname.js",
          "line": 1,
          "column": 0
        }
      },
      {
        "name": "bar",
        "id": "x^bar",
        "type": "string",
        "loc": {
          "file": "test/diffargname.js",
          "line": 1,
          "column": 0
        }
      }
    ],
    "loc": {
      "file": "test/diffargname.js",
      "line": 1,
      "column": 0
    }
  }
}


================================================
FILE: test/exported.js
================================================
// :: () → string
export function hello() { return "hello" }

// :: number
export var x = 10

// :: string
export default "hi"


================================================
FILE: test/exported.json
================================================
{
  "hello": {
    "id": "hello",
    "type": "Function",
    "params": [],
    "returns": {
      "type": "string",
      "id": "hello^returns",
      "loc": {
        "file": "test/exported.js",
        "line": 1,
        "column": 0
      }
    },
    "loc": {
      "file": "test/exported.js",
      "line": 1,
      "column": 0
    },
    "exported": true
  },
  "x": {
    "id": "x",
    "type": "number",
    "loc": {
      "file": "test/exported.js",
      "line": 4,
      "column": 0
    },
    "exported": true
  },
  "default": {
    "id": "default",
    "type": "string",
    "loc": {
      "file": "test/exported.js",
      "line": 7,
      "column": 0
    },
    "exported": true
  }
}


================================================
FILE: test/function.js
================================================
// :: (number, number) → number
// Adds two numbers
function add(a, b) {
  return a + b
}


================================================
FILE: test/function.json
================================================
{
  "add": {
    "id": "add",
    "type": "Function",
    "params": [
      {
        "type": "number",
        "name": "a",
        "id": "add^a",
        "loc": {
          "file": "test/function.js",
          "line": 1,
          "column": 0
        }
      },
      {
        "type": "number",
        "name": "b",
        "id": "add^b",
        "loc": {
          "file": "test/function.js",
          "line": 1,
          "column": 0
        }
      }
    ],
    "returns": {
      "type": "number",
      "id": "add^returns",
      "loc": {
        "file": "test/function.js",
        "line": 1,
        "column": 0
      }
    },
    "description": "Adds two numbers",
    "loc": {
      "file": "test/function.js",
      "line": 1,
      "column": 0
    }
  }
}


================================================
FILE: test/functionsub.js
================================================
// :: (number, number) → number
// A function
//
//   a::- Parameter a
//   return::- The return value is the sum
function foo(a, b) {
  return a + b
}


================================================
FILE: test/functionsub.json
================================================
{
  "foo": {
    "id": "foo",
    "loc": {
      "line": 1,
      "column": 0,
      "file": "test/functionsub.js"
    },
    "type": "Function",
    "params": [
      {
        "loc": {
          "line": 5,
          "column": 0,
          "file": "test/functionsub.js"
        },
        "type": "number",
        "name": "a",
        "id": "foo^a",
        "description": "Parameter a"
      },
      {
        "loc": {
          "line": 1,
          "column": 0,
          "file": "test/functionsub.js"
        },
        "type": "number",
        "name": "b",
        "id": "foo^b"
      }
    ],
    "returns": {
      "loc": {
        "line": 7,
        "column": 0,
        "file": "test/functionsub.js"
      },
      "type": "number",
      "id": "foo^returns",
      "description": "The return value is the sum"
    },
    "description": "A function\n"
  }
}


================================================
FILE: test/infer.js
================================================
// ::- It's x
var x = 10

// ::- It's y
var y = true

// ::- It's z
var z = /foo/

// ::- It's obj
var obj = new Something


================================================
FILE: test/infer.json
================================================
{
  "x": {
    "id": "x",
    "loc": {
      "line": 1,
      "column": 0,
      "file": "test/infer.js"
    },
    "description": "It's x",
    "type": "number"
  },
  "y": {
    "id": "y",
    "loc": {
      "line": 4,
      "column": 0,
      "file": "test/infer.js"
    },
    "description": "It's y",
    "type": "bool"
  },
  "z": {
    "id": "z",
    "loc": {
      "line": 7,
      "column": 0,
      "file": "test/infer.js"
    },
    "description": "It's z",
    "type": "RegExp"
  },
  "obj": {
    "id": "obj",
    "loc": {
      "line": 10,
      "column": 0,
      "file": "test/infer.js"
    },
    "description": "It's obj",
    "type": "Something"
  }
}


================================================
FILE: test/literal.js
================================================
// :: (union<"a","b">, number, number) → number
// Returns a or b
function add(which, a, b) {
  return which === "a" ? a : b
}


================================================
FILE: test/literal.json
================================================
{
  "add": {
    "id": "add",
    "loc": {
      "line": 1,
      "column": 0,
      "file": "test/literal.js"
    },
    "type": "Function",
    "params": [
      {
        "loc": {
          "line": 1,
          "column": 0,
          "file": "test/literal.js"
        },
        "type": "union",
        "typeParams": [
          {
            "loc": {
              "line": 1,
              "column": 0,
              "file": "test/literal.js"
            },
            "type": "\"a\""
          },
          {
            "loc": {
              "line": 1,
              "column": 0,
              "file": "test/literal.js"
            },
            "type": "\"b\""
          }
        ],
        "name": "which",
        "id": "add^which"
      },
      {
        "loc": {
          "line": 1,
          "column": 0,
          "file": "test/literal.js"
        },
        "type": "number",
        "name": "a",
        "id": "add^a"
      },
      {
        "loc": {
          "line": 1,
          "column": 0,
          "file": "test/literal.js"
        },
        "type": "number",
        "name": "b",
        "id": "add^b"
      }
    ],
    "returns": {
      "loc": {
        "line": 1,
        "column": 0,
        "file": "test/literal.js"
      },
      "type": "number",
      "id": "add^returns"
    },
    "description": "Returns a or b"
  }
}


================================================
FILE: test/namedprop.js
================================================
// ::- Boo the class
class Boo {
  // someProp:: (number) Some prop
}



================================================
FILE: test/namedprop.json
================================================
{
  "Boo": {
    "id": "Boo",
    "loc": {
      "line": 1,
      "column": 0,
      "file": "test/namedprop.js"
    },
    "type": "class",
    "description": "Boo the class",
    "properties": {
      "someProp": {
        "id": "Boo.someProp",
        "loc": {
          "line": 3,
          "column": 2,
          "file": "test/namedprop.js"
        },
        "type": "Function",
        "params": [
          {
            "loc": {
              "line": 3,
              "column": 2,
              "file": "test/namedprop.js"
            },
            "type": "number"
          }
        ],
        "description": "Some prop"
      }
    }
  }
}


================================================
FILE: test/obj.js
================================================
// :: {extra: number}
let obj = {
  // :: number A property
  foo: 10,
  bar: 20
}

// ::- An added property
obj.baz = 30


================================================
FILE: test/obj.json
================================================
{
  "obj": {
    "id": "obj",
    "type": "Object",
    "properties": {
      "extra": {
        "id": "obj.extra",
        "type": "number",
        "loc": {
          "file": "test/obj.js",
          "line": 1,
          "column": 0
        }
      },
      "foo": {
        "id": "obj.foo",
        "description": "A property",
        "type": "number",
        "loc": {
          "file": "test/obj.js",
          "line": 3,
          "column": 2
        }
      },
      "baz": {
        "id": "obj.baz",
        "description": "An added property",
        "type": "number",
        "loc": {
          "file": "test/obj.js",
          "line": 8,
          "column": 0
        }
      }
    },
    "loc": {
      "file": "test/obj.js",
      "line": 1,
      "column": 0
    }
  }
}


================================================
FILE: test/phantom.js
================================================
// Abc:: class
// The Abc class

// Abc.m1:: (a: number) → number
// Method #1

// Abc.m2:: () #static
// Method #2

// myString:: string
// My string


================================================
FILE: test/phantom.json
================================================
{
  "Abc": {
    "id": "Abc",
    "loc": {
      "line": 1,
      "column": 0,
      "file": "test/phantom.js"
    },
    "description": "The Abc class",
    "type": "class",
    "properties": {
      "m1": {
        "id": "Abc.m1",
        "loc": {
          "line": 4,
          "column": 0,
          "file": "test/phantom.js"
        },
        "type": "Function",
        "params": [
          {
            "loc": {
              "line": 4,
              "column": 0,
              "file": "test/phantom.js"
            },
            "type": "number",
            "name": "a",
            "id": "Abc.m1^a"
          }
        ],
        "returns": {
          "loc": {
            "line": 4,
            "column": 0,
            "file": "test/phantom.js"
          },
          "type": "number",
          "id": "Abc.m1^returns"
        },
        "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": {
    "id": "myString",
    "loc": {
      "line": 10,
      "column": 0,
      "file": "test/phantom.js"
    },
    "type": "string",
    "description": "My string"
  }
}


================================================
FILE: test/restarg.js
================================================
// :: ([string]) -> bool
function bar(...stuff) {}


================================================
FILE: test/restarg.json
================================================
{
  "bar": {
    "id": "bar",
    "type": "Function",
    "params": [
      {
        "type": "Array",
        "typeParams": [{
          "type": "string",
          "loc": {
            "file": "test/restarg.js",
            "line": 1,
            "column": 0
          }
        }],
        "rest": true,
        "name": "stuff",
        "id": "bar^stuff",
        "loc": {
          "file": "test/restarg.js",
          "line": 1,
          "column": 0
        }
      }
    ],
    "returns": {
      "type": "bool",
      "id": "bar^returns",
      "loc": {
        "file": "test/restarg.js",
        "line": 1,
        "column": 0
      }
    },
    "loc": {
      "file": "test/restarg.js",
      "line": 1,
      "column": 0
    }
  }
}


================================================
FILE: test/run.js
================================================
var fs = require("fs")

var getdocs = require("../src")

var filter = process.argv[2]

fs.readdirSync(__dirname).forEach(function(filename) {
  var isJSON = /^([^\.]+)\.json$/.exec(filename)
  if (!isJSON || (filter && isJSON[1].indexOf(filter) != 0)) return

  var expected = JSON.parse(fs.readFileSync(__dirname + "/" + filename, "utf8"))
  var jsfile = "/" + isJSON[1] + ".js"
  var returned = getdocs.gather(fs.readFileSync(__dirname + jsfile, "utf8"), {filename: "test" + jsfile})
  try {
    compare(returned, expected, "")
  } catch(e) {
    console.error(isJSON[1] + ": " + e.message)
    console.error("in " + JSON.stringify(returned, null, 2))
  }
})

function hop(obj, prop) {
  return Object.prototype.hasOwnProperty.call(obj, prop)
}

function compare(a, b, path) {
  if (typeof a != "object" || typeof b != "object") {
    if (a !== b) throw new Error("Mismatch at " + path + ": " + a + " vs " + b)
  } else {
    for (var prop in a) if (hop(a, prop)) {
      if (!(prop in b))
        throw new Error("Unexpected property " + path + "." + prop)
      else
        compare(a[prop], b[prop], path + "." + prop)
    }
    for (var prop in b) if (hop(b, prop)) {
      if (!(prop in a))
        throw new Error("Missing property " + path + "." + prop)
    }
  }
}


================================================
FILE: test/subcomment.js
================================================
// :: Object An object
//
//   x:: number The x coordinate
//
//   y:: Object The y object
//
//     inner:: bool A property of `y`
var x = {}


================================================
FILE: test/subcomment.json
================================================
{
  "x": {
    "id": "x",
    "loc": {
      "line": 1,
      "column": 0,
      "file": "test/subcomment.js"
    },
    "type": "Object",
    "description": "An object\n",
    "properties": {
      "x": {
        "id": "x.x",
        "loc": {
          "line": 4,
          "column": 0,
          "file": "test/subcomment.js"
        },
        "type": "number",
        "description": "The x coordinate\n"
      },
      "y": {
        "id": "x.y",
        "loc": {
          "line": 7,
          "column": 0,
          "file": "test/subcomment.js"
        },
        "type": "Object",
        "description": "The y object\n",
        "properties": {
          "inner": {
            "id": "x.y.inner",
            "loc": {
              "line": 10,
              "column": 0,
              "file": "test/subcomment.js"
            },
            "type": "bool",
            "description": "A property of `y`"
          }
        }
      }
    }
  }
}


================================================
FILE: test/tags.js
================================================
// :: () #deprecated #exported=false #context="a \"string\""
// Hello!
function foo() {}


================================================
FILE: test/tags.json
================================================
{
  "foo": {
    "id": "foo",
    "loc": {
      "file": "test/tags.js",
      "line": 1,
      "column": 0
    },
    "$deprecated": "true",
    "$exported": "false",
    "$context": "a \"string\"",
    "description": "Hello!",
    "type": "Function",
    "params": []
  }
}


================================================
FILE: test/union.js
================================================
// :: (union<number, string>, ?union<bool, RegExp, [Error]>) → union<number, bool>
// That's one complex signature
function blah(x, y) {}


================================================
FILE: test/union.json
================================================
{
  "blah": {
    "id": "blah",
    "type": "Function",
    "params": [
      {
        "type": "union",
        "typeParams": [
          {
            "type": "number",
            "loc": {
              "file": "test/union.js",
              "line": 1,
              "column": 0
            }
          },
          {
            "type": "string",
            "loc": {
              "file": "test/union.js",
              "line": 1,
              "column": 0
            }
          }
        ],
        "name": "x",
        "id": "blah^x",
        "loc": {
          "file": "test/union.js",
          "line": 1,
          "column": 0
        }
      },
      {
        "type": "union",
        "typeParams": [
          {
            "type": "bool",
            "loc": {
              "file": "test/union.js",
              "line": 1,
              "column": 0
            }
          },
          {
            "type": "RegExp",
            "loc": {
              "file": "test/union.js",
              "line": 1,
              "column": 0
            }
          },
          {
            "type": "Array",
            "loc": {
              "file": "test/union.js",
              "line": 1,
              "column": 0
            },
            "typeParams": [{
              "type": "Error",
              "loc": {
                "file": "test/union.js",
                "line": 1,
                "column": 0
              }
            }]
          }
        ],
        "optional": true,
        "name": "y",
        "id": "blah^y",
        "loc": {
          "file": "test/union.js",
          "line": 1,
          "column": 0
        }
      }
    ],
    "returns": {
      "type": "union",
      "id": "blah^returns",
      "typeParams": [
        {
          "type": "number",
          "loc": {
            "file": "test/union.js",
            "line": 1,
            "column": 0
          }
        },
        {
          "type": "bool",
          "loc": {
            "file": "test/union.js",
            "line": 1,
            "column": 0
          }
        }
      ],
      "loc": {
        "file": "test/union.js",
        "line": 1,
        "column": 0
      }
    },
    "loc": {
      "file": "test/union.js",
      "line": 1,
      "column": 0
    },
    "description": "That's one complex signature"
  }
}
Download .txt
gitextract__rpuop8z/

├── .gitignore
├── LICENSE
├── README.md
├── bin/
│   └── getdocs.js
├── package.json
├── src/
│   ├── doccomments.js
│   ├── index.js
│   └── parsetype.js
└── test/
    ├── class_addmethod.js
    ├── class_addmethod.json
    ├── class_ctor.js
    ├── class_ctor.json
    ├── class_expr.js
    ├── class_expr.json
    ├── class_simple.js
    ├── class_simple.json
    ├── class_static.js
    ├── class_static.json
    ├── class_this.js
    ├── class_this.json
    ├── defaultarg.js
    ├── defaultarg.json
    ├── diffargname.js
    ├── diffargname.json
    ├── exported.js
    ├── exported.json
    ├── function.js
    ├── function.json
    ├── functionsub.js
    ├── functionsub.json
    ├── infer.js
    ├── infer.json
    ├── literal.js
    ├── literal.json
    ├── namedprop.js
    ├── namedprop.json
    ├── obj.js
    ├── obj.json
    ├── phantom.js
    ├── phantom.json
    ├── restarg.js
    ├── restarg.json
    ├── run.js
    ├── subcomment.js
    ├── subcomment.json
    ├── tags.js
    ├── tags.json
    ├── union.js
    └── union.json
Download .txt
SYMBOL INDEX (54 symbols across 20 files)

FILE: src/doccomments.js
  function strip (line 6) | function strip(lines) {
  function Found (line 84) | function Found() {}
  function c (line 88) | function c(node, _, override) {
  function c (line 108) | function c(node, _, override) {
  function parseComment (line 119) | function parseComment(text, loc) {
  function dropIndent (line 142) | function dropIndent(text) {
  function parseNestedComments (line 152) | function parseNestedComments(text, loc) {

FILE: src/index.js
  function applySubComment (line 48) | function applySubComment(parent, sub) {
  function getPath (line 69) | function getPath(ancestors) {
  function addNameToPath (line 114) | function addNameToPath(name, path, isStatic) {
  function addData (line 124) | function addData(top, path, data) {
  function raise (line 192) | function raise(msg, loc) {
  function propName (line 196) | function propName(node, force) {
  function inferParam (line 209) | function inferParam(n) {
  function ctorName (line 228) | function ctorName(name) {
  function inferFn (line 232) | function inferFn(node, data, name) {
  function inferClass (line 253) | function inferClass(node, data) {
  function inferExpr (line 263) | function inferExpr(node, data, name) {
  function extend (line 283) | function extend(from, to, path, overrideLoc) {
  function deref (line 299) | function deref(obj, name) {
  function assignIds (line 303) | function assignIds(obj, path) {
  function lvalPath (line 316) | function lvalPath(lval, ancestors) {
  function assignedPath (line 333) | function assignedPath(ancestors) {
  function findPrototype (line 343) | function findPrototype(ancestors) {
  function assignedName (line 353) | function assignedName(node) {
  function selfPath (line 360) | function selfPath(ancestors) {
  function parentPath (line 375) | function parentPath(ancestors) {
  function splitPath (line 385) | function splitPath(path) {

FILE: src/parsetype.js
  function isSpace (line 8) | function isSpace(ch) {
  function Input (line 12) | function Input(string, start, loc) {
  function parse (line 50) | function parse(input) {
  function parseBase (line 56) | function parseBase(input) {

FILE: test/class_addmethod.js
  class Foo (line 2) | class Foo {}

FILE: test/class_ctor.js
  function Point (line 2) | function Point(x, y) {

FILE: test/class_expr.js
  method m (line 4) | m(a, b) { return true }

FILE: test/class_simple.js
  class Foo (line 2) | class Foo extends Bar {
    method constructor (line 4) | constructor(a, b) {
    method m (line 10) | m(a, b) { return true }

FILE: test/class_static.js
  class Foo (line 2) | class Foo extends Bar {
    method m (line 4) | static m(a, b) { return true }

FILE: test/class_this.js
  class Foo (line 2) | class Foo {
    method constructor (line 4) | constructor(a) {

FILE: test/defaultarg.js
  function foo (line 2) | function foo(arg = false) {}

FILE: test/diffargname.js
  function x (line 2) | function x(a, b) {}

FILE: test/exported.js
  function hello (line 2) | function hello() { return "hello" }

FILE: test/function.js
  function add (line 3) | function add(a, b) {

FILE: test/functionsub.js
  function foo (line 6) | function foo(a, b) {

FILE: test/literal.js
  function add (line 3) | function add(which, a, b) {

FILE: test/namedprop.js
  class Boo (line 2) | class Boo {

FILE: test/restarg.js
  function bar (line 2) | function bar(...stuff) {}

FILE: test/run.js
  function hop (line 22) | function hop(obj, prop) {
  function compare (line 26) | function compare(a, b, path) {

FILE: test/tags.js
  function foo (line 3) | function foo() {}

FILE: test/union.js
  function blah (line 3) | function blah(x, y) {}
Condensed preview — 49 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (63K chars).
[
  {
    "path": ".gitignore",
    "chars": 22,
    "preview": "/node_modules\n.tern-*\n"
  },
  {
    "path": "LICENSE",
    "chars": 1100,
    "preview": "Copyright (C) 2013 by Marijn Haverbeke <marijn@haverbeke.berlin> and others\n\nPermission is hereby granted, free of charg"
  },
  {
    "path": "README.md",
    "chars": 7981,
    "preview": "# Getdocs\n\nGetdocs is like JSDoc or documentation.js, running over ES6 code to\nextract information and inline documentat"
  },
  {
    "path": "bin/getdocs.js",
    "chars": 354,
    "preview": "#!/usr/bin/env node\nvar fs = require(\"fs\")\nvar glob = require(\"glob\")\nvar getdocs = require(\"../src\")\n\nvar items = {}\n\np"
  },
  {
    "path": "package.json",
    "chars": 721,
    "preview": "{\n  \"name\": \"getdocs\",\n  \"version\": \"0.6.1\",\n  \"description\": \"An extractor for succinct documentation comments in JavaS"
  },
  {
    "path": "src/doccomments.js",
    "chars": 5553,
    "preview": "var acorn = require(\"acorn/dist/acorn\")\nvar walk = require(\"acorn/dist/walk\")\n\nvar parseType = require(\"./parsetype\")\n\nf"
  },
  {
    "path": "src/index.js",
    "chars": 12825,
    "preview": "var docComments = require(\"./doccomments\")\nexports.stripComment = docComments.stripComment\nvar parseType = exports.parse"
  },
  {
    "path": "src/parsetype.js",
    "chars": 3045,
    "preview": "module.exports = function(string, start, loc) {\n  var input = new Input(string, start, loc)\n  var result = parse(input)\n"
  },
  {
    "path": "test/class_addmethod.js",
    "chars": 112,
    "preview": "// ::- The Foo class\nclass Foo {}\n\n// :: (number, string) → bool\n// A method\nFoo.prototype.bar = (a, b) => true\n"
  },
  {
    "path": "test/class_addmethod.json",
    "chars": 1181,
    "preview": "{\n  \"Foo\": {\n    \"id\": \"Foo\",\n    \"description\": \"The Foo class\",\n    \"type\": \"class\",\n    \"loc\": {\n      \"file\": \"test/"
  },
  {
    "path": "test/class_ctor.js",
    "chars": 153,
    "preview": "// :: (number, number) A vector type\nfunction Point(x, y) {\n  // :: number The x coordinate\n  this.x = x\n  // :: number "
  },
  {
    "path": "test/class_ctor.json",
    "chars": 1342,
    "preview": "{\n  \"Point\": {\n    \"id\": \"Point\",\n    \"constructor\": {\n      \"id\": \"Point.constructor\",\n      \"type\": \"Function\",\n      "
  },
  {
    "path": "test/class_expr.js",
    "chars": 114,
    "preview": "// ::- The Foo class\nconst Foo = class extends Bar {\n  // :: (number, string) -> bool\n  m(a, b) { return true }\n}\n"
  },
  {
    "path": "test/class_expr.json",
    "chars": 1256,
    "preview": "{\n  \"Foo\": {\n    \"id\": \"Foo\",\n    \"description\": \"The Foo class\",\n    \"type\": \"class\",\n    \"loc\": {\n      \"file\": \"test/"
  },
  {
    "path": "test/class_simple.js",
    "chars": 188,
    "preview": "// ::- The Foo class\nclass Foo extends Bar {\n  // :: (number, number)\n  constructor(a, b) {\n    this.a = a\n    this.b = "
  },
  {
    "path": "test/class_simple.json",
    "chars": 1944,
    "preview": "{\n  \"Foo\": {\n    \"id\": \"Foo\",\n    \"description\": \"The Foo class\",\n    \"loc\": {\n      \"file\": \"test/class_simple.js\",\n   "
  },
  {
    "path": "test/class_static.js",
    "chars": 113,
    "preview": "// ::- The Foo class\nclass Foo extends Bar {\n  // :: (number, string) -> bool\n  static m(a, b) { return true }\n}\n"
  },
  {
    "path": "test/class_static.json",
    "chars": 1275,
    "preview": "{\n  \"Foo\": {\n    \"id\": \"Foo\",\n    \"description\": \"The Foo class\",\n    \"type\": \"class\",\n    \"loc\": {\n      \"file\": \"test/"
  },
  {
    "path": "test/class_this.js",
    "chars": 219,
    "preview": "// ::- A Foo\nclass Foo {\n  // :: (number)\n  constructor(a) {\n    // :: number\n    // The a property\n    this.a = a\n  }\n}"
  },
  {
    "path": "test/class_this.json",
    "chars": 1302,
    "preview": "{\n  \"Foo\": {\n    \"id\": \"Foo\",\n    \"loc\": {\n      \"file\": \"test/class_this.js\",\n      \"line\": 1,\n      \"column\": 0\n    },"
  },
  {
    "path": "test/defaultarg.js",
    "chars": 42,
    "preview": "// :: (bool)\nfunction foo(arg = false) {}\n"
  },
  {
    "path": "test/defaultarg.json",
    "chars": 425,
    "preview": "{\n  \"foo\": {\n    \"id\": \"foo\",\n    \"type\": \"Function\",\n    \"params\": [\n      {\n        \"type\": \"bool\",\n        \"default\":"
  },
  {
    "path": "test/diffargname.js",
    "chars": 53,
    "preview": "// :: (foo: number, bar: string)\nfunction x(a, b) {}\n"
  },
  {
    "path": "test/diffargname.json",
    "chars": 569,
    "preview": "{\n  \"x\": {\n    \"id\": \"x\",\n    \"type\": \"Function\",\n    \"params\": [\n      {\n        \"name\": \"foo\",\n        \"id\": \"x^foo\",\n"
  },
  {
    "path": "test/exported.js",
    "chars": 127,
    "preview": "// :: () → string\nexport function hello() { return \"hello\" }\n\n// :: number\nexport var x = 10\n\n// :: string\nexport defaul"
  },
  {
    "path": "test/exported.json",
    "chars": 701,
    "preview": "{\n  \"hello\": {\n    \"id\": \"hello\",\n    \"type\": \"Function\",\n    \"params\": [],\n    \"returns\": {\n      \"type\": \"string\",\n   "
  },
  {
    "path": "test/function.js",
    "chars": 90,
    "preview": "// :: (number, number) → number\n// Adds two numbers\nfunction add(a, b) {\n  return a + b\n}\n"
  },
  {
    "path": "test/function.json",
    "chars": 772,
    "preview": "{\n  \"add\": {\n    \"id\": \"add\",\n    \"type\": \"Function\",\n    \"params\": [\n      {\n        \"type\": \"number\",\n        \"name\": "
  },
  {
    "path": "test/functionsub.js",
    "chars": 152,
    "preview": "// :: (number, number) → number\n// A function\n//\n//   a::- Parameter a\n//   return::- The return value is the sum\nfuncti"
  },
  {
    "path": "test/functionsub.json",
    "chars": 870,
    "preview": "{\n  \"foo\": {\n    \"id\": \"foo\",\n    \"loc\": {\n      \"line\": 1,\n      \"column\": 0,\n      \"file\": \"test/functionsub.js\"\n    }"
  },
  {
    "path": "test/infer.js",
    "chars": 123,
    "preview": "// ::- It's x\nvar x = 10\n\n// ::- It's y\nvar y = true\n\n// ::- It's z\nvar z = /foo/\n\n// ::- It's obj\nvar obj = new Somethi"
  },
  {
    "path": "test/infer.json",
    "chars": 671,
    "preview": "{\n  \"x\": {\n    \"id\": \"x\",\n    \"loc\": {\n      \"line\": 1,\n      \"column\": 0,\n      \"file\": \"test/infer.js\"\n    },\n    \"des"
  },
  {
    "path": "test/literal.js",
    "chars": 127,
    "preview": "// :: (union<\"a\",\"b\">, number, number) → number\n// Returns a or b\nfunction add(which, a, b) {\n  return which === \"a\" ? a"
  },
  {
    "path": "test/literal.json",
    "chars": 1363,
    "preview": "{\n  \"add\": {\n    \"id\": \"add\",\n    \"loc\": {\n      \"line\": 1,\n      \"column\": 0,\n      \"file\": \"test/literal.js\"\n    },\n  "
  },
  {
    "path": "test/namedprop.js",
    "chars": 71,
    "preview": "// ::- Boo the class\nclass Boo {\n  // someProp:: (number) Some prop\n}\n\n"
  },
  {
    "path": "test/namedprop.json",
    "chars": 654,
    "preview": "{\n  \"Boo\": {\n    \"id\": \"Boo\",\n    \"loc\": {\n      \"line\": 1,\n      \"column\": 0,\n      \"file\": \"test/namedprop.js\"\n    },\n"
  },
  {
    "path": "test/obj.js",
    "chars": 122,
    "preview": "// :: {extra: number}\nlet obj = {\n  // :: number A property\n  foo: 10,\n  bar: 20\n}\n\n// ::- An added property\nobj.baz = 3"
  },
  {
    "path": "test/obj.json",
    "chars": 786,
    "preview": "{\n  \"obj\": {\n    \"id\": \"obj\",\n    \"type\": \"Object\",\n    \"properties\": {\n      \"extra\": {\n        \"id\": \"obj.extra\",\n    "
  },
  {
    "path": "test/phantom.js",
    "chars": 151,
    "preview": "// Abc:: class\n// The Abc class\n\n// Abc.m1:: (a: number) → number\n// Method #1\n\n// Abc.m2:: () #static\n// Method #2\n\n// "
  },
  {
    "path": "test/phantom.json",
    "chars": 1386,
    "preview": "{\n  \"Abc\": {\n    \"id\": \"Abc\",\n    \"loc\": {\n      \"line\": 1,\n      \"column\": 0,\n      \"file\": \"test/phantom.js\"\n    },\n  "
  },
  {
    "path": "test/restarg.js",
    "chars": 51,
    "preview": "// :: ([string]) -> bool\nfunction bar(...stuff) {}\n"
  },
  {
    "path": "test/restarg.json",
    "chars": 744,
    "preview": "{\n  \"bar\": {\n    \"id\": \"bar\",\n    \"type\": \"Function\",\n    \"params\": [\n      {\n        \"type\": \"Array\",\n        \"typePara"
  },
  {
    "path": "test/run.js",
    "chars": 1275,
    "preview": "var fs = require(\"fs\")\n\nvar getdocs = require(\"../src\")\n\nvar filter = process.argv[2]\n\nfs.readdirSync(__dirname).forEach"
  },
  {
    "path": "test/subcomment.js",
    "chars": 143,
    "preview": "// :: Object An object\n//\n//   x:: number The x coordinate\n//\n//   y:: Object The y object\n//\n//     inner:: bool A prop"
  },
  {
    "path": "test/subcomment.json",
    "chars": 954,
    "preview": "{\n  \"x\": {\n    \"id\": \"x\",\n    \"loc\": {\n      \"line\": 1,\n      \"column\": 0,\n      \"file\": \"test/subcomment.js\"\n    },\n   "
  },
  {
    "path": "test/tags.js",
    "chars": 89,
    "preview": "// :: () #deprecated #exported=false #context=\"a \\\"string\\\"\"\n// Hello!\nfunction foo() {}\n"
  },
  {
    "path": "test/tags.json",
    "chars": 276,
    "preview": "{\n  \"foo\": {\n    \"id\": \"foo\",\n    \"loc\": {\n      \"file\": \"test/tags.js\",\n      \"line\": 1,\n      \"column\": 0\n    },\n    \""
  },
  {
    "path": "test/union.js",
    "chars": 138,
    "preview": "// :: (union<number, string>, ?union<bool, RegExp, [Error]>) → union<number, bool>\n// That's one complex signature\nfunct"
  },
  {
    "path": "test/union.json",
    "chars": 2332,
    "preview": "{\n  \"blah\": {\n    \"id\": \"blah\",\n    \"type\": \"Function\",\n    \"params\": [\n      {\n        \"type\": \"union\",\n        \"typePa"
  }
]

About this extraction

This page contains the full source code of the marijnh/getdocs GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 49 files (54.7 KB), approximately 16.5k tokens, and a symbol index with 54 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!