[Pkg-javascript-commits] [node-doctrine] 01/02: New upstream version 1.5.0

Julien Puydt julien.puydt at laposte.net
Sun Oct 16 08:58:50 UTC 2016


This is an automated email from the git hooks/post-receive script.

jpuydt-guest pushed a commit to branch master
in repository node-doctrine.

commit 6fcd8bbadc6c7049a5b5e0c8006bb05f348c9f0d
Author: Julien Puydt <julien.puydt at laposte.net>
Date:   Sun Oct 16 10:13:33 2016 +0200

    New upstream version 1.5.0
---
 .editorconfig             |    8 +
 .eslintrc.js              |  207 ++++
 .github/issue_template.md |   14 +
 .gitignore                |    6 +
 .jshintrc                 |   28 +
 .scripted                 |    6 +
 .travis.yml               |   10 +
 CHANGELOG.md              |   70 ++
 CONTRIBUTING.md           |    5 +
 LICENSE.BSD               |   22 +
 LICENSE.closure-compiler  |  202 ++++
 LICENSE.esprima           |   19 +
 Makefile.js               |   87 ++
 README.md                 |  174 +++
 lib/doctrine.js           |  897 ++++++++++++++
 lib/typed.js              | 1283 ++++++++++++++++++++
 lib/utility.js            |   35 +
 package.json              |   65 +
 test/midstream.js         |   43 +
 test/parse.js             | 2903 +++++++++++++++++++++++++++++++++++++++++++++
 test/strict.js            |  149 +++
 test/stringify.js         |  423 +++++++
 test/test.html            |   31 +
 test/unwrap.js            |   41 +
 24 files changed, 6728 insertions(+)

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..2e62126
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+end_of_line = lf
+insert_final_newline = true
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..01e7414
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,207 @@
+module.exports = {
+    "env": {
+        "node": true
+    },
+    "rules": {
+        "accessor-pairs": 2,
+        "array-bracket-spacing": 2,
+        "arrow-body-style": 2,
+        "arrow-parens": 2,
+        "arrow-spacing": 2,
+        "block-scoped-var": 2,
+        "block-spacing": 2,
+        "brace-style": [
+            2,
+            "1tbs"
+        ],
+        "camelcase": 2,
+        "comma-dangle": [
+            2,
+            "never"
+        ],
+        "comma-spacing": [
+            2,
+            {
+                "after": true,
+                "before": false
+            }
+        ],
+        "comma-style": 2,
+        "complexity": 2,
+        "computed-property-spacing": [
+            2,
+            "never"
+        ],
+        "consistent-this": 2,
+        "constructor-super": 2,
+        "curly": 2,
+        "dot-location": 2,
+        "dot-notation": [
+            2,
+            {
+                "allowKeywords": false
+            }
+        ],
+        "eol-last": 2,
+        "generator-star-spacing": 2,
+        "guard-for-in": 2,
+        "handle-callback-err": 2,
+        "id-match": 2,
+        "indent": 2,
+        "jsx-quotes": 2,
+        "key-spacing": 2,
+        "linebreak-style": [
+            2,
+            "unix"
+        ],
+        "max-nested-callbacks": 2,
+        "new-cap": 2,
+        "new-parens": 2,
+        "no-alert": 2,
+        "no-array-constructor": 2,
+        "no-arrow-condition": 2,
+        "no-bitwise": 2,
+        "no-caller": 2,
+        "no-case-declarations": 2,
+        "no-catch-shadow": 2,
+        "no-class-assign": 2,
+        "no-cond-assign": 2,
+        "no-console": 2,
+        "no-const-assign": 2,
+        "no-continue": 2,
+        "no-control-regex": 2,
+        "no-debugger": 2,
+        "no-delete-var": 2,
+        "no-div-regex": 2,
+        "no-dupe-args": 2,
+        "no-dupe-class-members": 2,
+        "no-dupe-keys": 2,
+        "no-duplicate-case": 2,
+        "no-empty": 2,
+        "no-empty-character-class": 2,
+        "no-empty-label": 2,
+        "no-empty-pattern": 2,
+        "no-eval": 2,
+        "no-ex-assign": 2,
+        "no-extend-native": 2,
+        "no-extra-bind": 2,
+        "no-extra-boolean-cast": 2,
+        "no-extra-parens": [
+            2,
+            "functions"
+        ],
+        "no-extra-semi": 2,
+        "no-fallthrough": 2,
+        "no-floating-decimal": 2,
+        "no-func-assign": 2,
+        "no-implicit-coercion": 2,
+        "no-implied-eval": 2,
+        "no-inner-declarations": [
+            2,
+            "functions"
+        ],
+        "no-invalid-regexp": 2,
+        "no-invalid-this": 2,
+        "no-irregular-whitespace": 2,
+        "no-iterator": 2,
+        "no-label-var": 2,
+        "no-labels": 2,
+        "no-lone-blocks": 2,
+        "no-loop-func": 2,
+        "no-mixed-requires": 2,
+        "no-mixed-spaces-and-tabs": 2,
+        "no-multi-str": 2,
+        "no-multiple-empty-lines": 2,
+        "no-native-reassign": 2,
+        "no-negated-in-lhs": 2,
+        "no-nested-ternary": 2,
+        "no-new": 2,
+        "no-new-func": 2,
+        "no-new-object": 2,
+        "no-new-require": 2,
+        "no-new-wrappers": 2,
+        "no-obj-calls": 2,
+        "no-octal": 2,
+        "no-octal-escape": 2,
+        "no-path-concat": 2,
+        "no-process-env": 2,
+        "no-process-exit": 2,
+        "no-proto": 2,
+        "no-redeclare": 2,
+        "no-regex-spaces": 2,
+        "no-restricted-modules": 2,
+        "no-restricted-syntax": 2,
+        "no-return-assign": 2,
+        "no-script-url": 2,
+        "no-self-compare": 2,
+        "no-sequences": 2,
+        "no-shadow-restricted-names": 2,
+        "no-spaced-func": 2,
+        "no-sparse-arrays": 2,
+        "no-sync": 2,
+        "no-this-before-super": 2,
+        "no-throw-literal": 2,
+        "no-trailing-spaces": 2,
+        "no-undef": 2,
+        "no-undef-init": 2,
+        "no-unexpected-multiline": 2,
+        "no-unneeded-ternary": 2,
+        "no-unreachable": 2,
+        "no-unused-expressions": 2,
+        "no-unused-vars": 2,
+        "no-use-before-define": [
+            2,
+            "nofunc"
+        ],
+        "no-useless-call": 2,
+        "no-useless-concat": 2,
+        "no-void": 2,
+        "no-with": 2,
+        "object-curly-spacing": 2,
+        "operator-assignment": [
+            2,
+            "always"
+        ],
+        "operator-linebreak": 2,
+        "prefer-const": 2,
+        "prefer-spread": 2,
+        "quotes": [
+            2,
+            "single"
+        ],
+        "radix": 2,
+        "require-yield": 2,
+        "semi": [
+            2,
+            "always"
+        ],
+        "semi-spacing": [
+            2,
+            {
+                "after": true,
+                "before": false
+            }
+        ],
+        "space-before-keywords": [
+            2,
+            "always"
+        ],
+        "space-in-parens": 2,
+        "space-infix-ops": 2,
+        "space-return-throw-case": 2,
+        "space-unary-ops": 2,
+        "strict": [
+            2,
+            "function"
+        ],
+        "use-isnan": 2,
+        "valid-jsdoc": 2,
+        "valid-typeof": 2,
+        "wrap-iife": 2,
+        "wrap-regex": 2,
+        "yoda": [
+            2,
+            "never"
+        ]
+    }
+};
diff --git a/.github/issue_template.md b/.github/issue_template.md
new file mode 100644
index 0000000..e74389f
--- /dev/null
+++ b/.github/issue_template.md
@@ -0,0 +1,14 @@
+**What version are you using?**
+
+
+**What JSDoc comment were you trying to parse?**
+
+
+**What Doctrine options were you using (sloppy, unwrap, etc.)?**
+
+
+**What happened? Include output if possible.**
+
+
+**What did you expect to happen?**
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5ca5bc3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+.idea
+*.iml
+coverage
+.cache
+npm-debug.log
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..3ecc7bc
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,28 @@
+{
+    "curly": true,
+    "eqeqeq": true,
+    "immed": true,
+    "indent": 4,
+    "eqnull": true,
+    "latedef": true,
+    "noarg": true,
+    "noempty": true,
+    "quotmark": "single",
+    "undef": true,
+    "unused": "vars",
+    "strict": true,
+    "trailing": true,
+    "validthis": true,
+    "bitwise": true,
+    "plusplus": false,
+    "nomen": true,
+
+    "onevar": true,
+
+    "node": true,
+    "predef": [
+        "doctrine",
+        "parseTypeExpression",
+        "parseTop"
+    ]
+}
diff --git a/.scripted b/.scripted
new file mode 100644
index 0000000..89683cd
--- /dev/null
+++ b/.scripted
@@ -0,0 +1,6 @@
+{
+        "editor": {
+            "expandtab": true,
+			"tabsize": 4
+	}
+}
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..fe5b0e2
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,10 @@
+language: node_js
+node_js:
+    - "0.10"
+    - "0.12"
+    - iojs
+    - "4"
+sudo: false
+script: "npm test"
+after_success:
+    - npm run coveralls
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..63fa77c
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,70 @@
+v1.5.0 - October 13, 2016
+
+* e33c6bb Update: Add support for BooleanLiteralType (#173) (Erik Arvidsson)
+
+v1.4.0 - September 13, 2016
+
+* d7426e5 Update: add ability to parse optional properties in typedefs (refs #5) (#174) (ikokostya)
+
+v1.3.0 - August 22, 2016
+
+* 12c7ad9 Update: Add support for numeric and string literal types (fixes #156) (#172) (Andrew Walter)
+
+v1.2.3 - August 16, 2016
+
+* b96a884 Build: Add CI release script (Nicholas C. Zakas)
+* 8d9b3c7 Upgrade: Upgrade esutils to v2.0.2 (fixes #170) (#171) (Emeegeemee)
+
+v1.2.2 - May 19, 2016
+
+* ebe0b08 Fix: Support case insensitive tags (fixes #163) (#164) (alberto)
+* 8e6d81e Chore: Remove copyright and license from headers (Nicholas C. Zakas)
+* 79035c6 Chore: Include jQuery Foundation copyright (Nicholas C. Zakas)
+* 06910a7 Fix: Preserve whitespace in default param string values (fixes #157) (Kai Cataldo)
+
+v1.2.1 - March 29, 2016
+
+* 1f54014 Fix: allow hyphens in names (fixes #116) (Kai Cataldo)
+* bbee469 Docs: Add issue template (Nicholas C. Zakas)
+
+v1.2.0 - February 19, 2016
+
+* 18136c5 Build: Cleanup build system (Nicholas C. Zakas)
+* b082f85 Update: Add support for slash in namepaths (fixes #100) (Ryan Duffy)
+* def53a2 Docs: Fix typo in option lineNumbers (Daniel Tschinder)
+* e2cbbc5 Update: Bump isarray to v1.0.0 (Shinnosuke Watanabe)
+* ae07aa8 Fix: Allow whitespace in optional param with default value (fixes #141) (chris)
+
+v1.1.0 - January 6, 2016
+
+* Build: Switch to Makefile.js (Nicholas C. Zakas)
+* New: support name expression for @this tag (fixes #143) (Tim Schaub)
+* Build: Update ESLint settings (Nicholas C. Zakas)
+
+v1.0.0 - December 21, 2015
+
+* New: parse caption tags in examples into separate property. (fixes #131) (Tom MacWright)
+
+v0.7.2 - November 27, 2015
+
+* Fix: Line numbers for some tags (fixes #138) Fixing issue where input was not consumed via advance() but was skipped when parsing tags resulting in sometimes incorrect reported lineNumber. (TEHEK)
+* Build: Add missing linefix package (Nicholas C. Zakas)
+
+v0.7.1 - November 13, 2015
+
+* Update: Begin switch to Makefile.js (Nicholas C. Zakas)
+* Fix: permit return tag without type (fixes #136) (Tom MacWright)
+* Fix: package.json homepage field (Bogdan Chadkin)
+* Fix: Parse array default syntax. Fixes #133 (Tom MacWright)
+* Fix: Last tag always has \n in the description (fixes #87) (Burak Yigit Kaya)
+* Docs: Add changelog (Nicholas C. Zakas)
+
+v0.7.0 - September 21, 2015
+
+* Docs: Update README with new info (fixes #127) (Nicholas C. Zakas)
+* Fix: Parsing fix for param with arrays and properties (fixes #111) (Gyandeep Singh)
+* Build: Add travis build (fixes #123) (Gyandeep Singh)
+* Fix: Parsing of parameter name without a type (fixes #120) (Gyandeep Singh)
+* New: added preserveWhitespace option (Aleks Totic)
+* New: Add "files" entry to only deploy select files (Rob Loach)
+* New: Add support and tests for typedefs. Refs #5 (Tom MacWright)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..6e09784
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,5 @@
+Project license(s): 2-clause BSD license
+
+* You will only Submit Contributions where You have authored 100% of the content.
+* You will only Submit Contributions to which You have the necessary rights. This means that if You are employed You have received the necessary permissions from Your employer to make the Contributions.
+* Whatever content You Contribute will be provided under the Project License(s).
diff --git a/LICENSE.BSD b/LICENSE.BSD
new file mode 100644
index 0000000..1e03b5d
--- /dev/null
+++ b/LICENSE.BSD
@@ -0,0 +1,22 @@
+Doctrine
+Copyright jQuery Foundation and other contributors, https://jquery.org/
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/LICENSE.closure-compiler b/LICENSE.closure-compiler
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE.closure-compiler
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/LICENSE.esprima b/LICENSE.esprima
new file mode 100644
index 0000000..3e580c3
--- /dev/null
+++ b/LICENSE.esprima
@@ -0,0 +1,19 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile.js b/Makefile.js
new file mode 100644
index 0000000..c814824
--- /dev/null
+++ b/Makefile.js
@@ -0,0 +1,87 @@
+/**
+ * @fileoverview Build file
+ * @author nzakas
+ */
+/* global exec, exit, find, target*/
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+require("shelljs/make");
+
+var nodeCLI = require("shelljs-nodecli");
+
+//------------------------------------------------------------------------------
+// Data
+//------------------------------------------------------------------------------
+
+var NODE_MODULES = "./node_modules/",
+
+    // Utilities - intentional extra space at the end of each string
+    MOCHA = NODE_MODULES + "mocha/bin/_mocha ",
+
+    // Files
+    /* eslint-disable no-use-before-define */
+    TEST_FILES = find("test/").filter(fileType("js")).join(" "),
+    /* eslint-enable no-use-before-define */
+
+    // Settings
+    MOCHA_TIMEOUT = 4000;
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Generates a function that matches files with a particular extension.
+ * @param {string} extension The file extension (i.e. "js")
+ * @returns {Function} The function to pass into a filter method.
+ * @private
+ */
+function fileType(extension) {
+    return function(filename) {
+        return filename.substring(filename.lastIndexOf(".") + 1) === extension;
+    };
+}
+
+/**
+ * Splits a command result to separate lines.
+ * @param {string} result The command result string.
+ * @returns {array} The separated lines.
+ */
+function splitCommandResultToLines(result) {
+    return result.trim().split("\n");
+}
+
+//------------------------------------------------------------------------------
+// Tasks
+//------------------------------------------------------------------------------
+
+target.all = function() {
+    target.test();
+};
+
+target.test = function() {
+
+    var errors = 0,
+        lastReturn;
+
+    lastReturn = nodeCLI.exec("istanbul", "cover", MOCHA, "-- -R dot -t " + MOCHA_TIMEOUT, "-c", TEST_FILES);
+    if (lastReturn.code !== 0) {
+        errors++;
+    }
+
+    lastReturn = nodeCLI.exec("istanbul", "check-coverage", "--statement 99 --branch 97 --function 100 --lines 99");
+    if (lastReturn.code !== 0) {
+        errors++;
+    }
+
+    if (errors) {
+        exit(1);
+    }
+
+};
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bc6dcf9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,174 @@
+[![NPM version][npm-image]][npm-url]
+[![build status][travis-image]][travis-url]
+[![Test coverage][coveralls-image]][coveralls-url]
+[![Downloads][downloads-image]][downloads-url]
+[![Join the chat at https://gitter.im/eslint/doctrine](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/eslint/doctrine?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+# Doctrine
+
+Doctrine is a [JSDoc](http://usejsdoc.org) parser that parses documentation comments from JavaScript (you need to pass in the comment, not a whole JavaScript file).
+
+## Installation
+
+You can install Doctrine using [npm](https://npmjs.com):
+
+```
+$ npm install doctrine --save-dev
+```
+
+Doctrine can also be used in web browsers using [Browserify](http://browserify.org).
+
+## Usage
+
+Require doctrine inside of your JavaScript:
+
+```js
+var doctrine = require("doctrine");
+```
+
+### parse()
+
+The primary method is `parse()`, which accepts two arguments: the JSDoc comment to parse and an optional options object. The available options are:
+
+* `unwrap` - set to `true` to delete the leading `/**`, any `*` that begins a line, and the trailing `*/` from the source text. Default: `false`.
+* `tags` - an array of tags to return. When specified, Doctrine returns only tags in this array. For example, if `tags` is `["param"]`, then only `@param` tags will be returned. Default: `null`.
+* `recoverable` - set to `true` to keep parsing even when syntax errors occur. Default: `false`.
+* `sloppy` - set to `true` to allow optional parameters to be specified in brackets (`@param {string} [foo]`). Default: `false`.
+* `lineNumbers` - set to `true` to add `lineNumber` to each node, specifying the line on which the node is found in the source. Default: `false`.
+
+Here's a simple example:
+
+```js
+var ast = doctrine.parse(
+    [
+        "/**",
+        " * This function comment is parsed by doctrine",
+        " * @param {{ok:String}} userName",
+        "*/"
+    ].join('\n'), { unwrap: true });
+```
+
+This example returns the following AST:
+
+    {
+        "description": "This function comment is parsed by doctrine",
+        "tags": [
+            {
+                "title": "param",
+                "description": null,
+                "type": {
+                    "type": "RecordType",
+                    "fields": [
+                        {
+                            "type": "FieldType",
+                            "key": "ok",
+                            "value": {
+                                "type": "NameExpression",
+                                "name": "String"
+                            }
+                        }
+                    ]
+                },
+                "name": "userName"
+            }
+        ]
+    }
+
+See the [demo page](http://eslint.org/doctrine/demo/) more detail.
+
+## Team
+
+These folks keep the project moving and are resources for help:
+
+* Nicholas C. Zakas ([@nzakas](https://github.com/nzakas)) - project lead
+* Yusuke Suzuki ([@constellation](https://github.com/constellation)) - reviewer
+
+## Contributing
+
+Issues and pull requests will be triaged and responded to as quickly as possible. We operate under the [ESLint Contributor Guidelines](http://eslint.org/docs/developer-guide/contributing), so please be sure to read them before contributing. If you're not sure where to dig in, check out the [issues](https://github.com/eslint/doctrine/issues).
+
+## Frequently Asked Questions
+
+### Can I pass a whole JavaScript file to Doctrine?
+
+No. Doctrine can only parse JSDoc comments, so you'll need to pass just the JSDoc comment to Doctrine in order to work.
+
+
+### License
+
+#### doctrine
+
+Copyright (C) 2012 [Yusuke Suzuki](http://github.com/Constellation)
+ (twitter: [@Constellation](http://twitter.com/Constellation)) and other contributors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+  * Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#### esprima
+
+some of functions is derived from esprima
+
+Copyright (C) 2012, 2011 [Ariya Hidayat](http://ariya.ofilabs.com/about)
+ (twitter: [@ariyahidayat](http://twitter.com/ariyahidayat)) and other contributors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+  * Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+#### closure-compiler
+
+some of extensions is derived from closure-compiler
+
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+
+### Where to ask for help?
+
+Join our [Chatroom](https://gitter.im/eslint/doctrine)
+
+[npm-image]: https://img.shields.io/npm/v/doctrine.svg?style=flat-square
+[npm-url]: https://www.npmjs.com/package/doctrine
+[travis-image]: https://img.shields.io/travis/eslint/doctrine/master.svg?style=flat-square
+[travis-url]: https://travis-ci.org/eslint/doctrine
+[coveralls-image]: https://img.shields.io/coveralls/eslint/doctrine/master.svg?style=flat-square
+[coveralls-url]: https://coveralls.io/r/eslint/doctrine?branch=master
+[downloads-image]: http://img.shields.io/npm/dm/doctrine.svg?style=flat-square
+[downloads-url]: https://www.npmjs.com/package/doctrine
diff --git a/lib/doctrine.js b/lib/doctrine.js
new file mode 100644
index 0000000..095eeeb
--- /dev/null
+++ b/lib/doctrine.js
@@ -0,0 +1,897 @@
+/*
+ * @fileoverview Main Doctrine object
+ * @author Yusuke Suzuki <utatane.tea at gmail.com>
+ * @author Dan Tao <daniel.tao at gmail.com>
+ * @author Andrew Eisenberg <andrew at eisenberg.as>
+ */
+
+(function () {
+    'use strict';
+
+    var typed,
+        utility,
+        isArray,
+        jsdoc,
+        esutils,
+        hasOwnProperty;
+
+    esutils = require('esutils');
+    isArray = require('isarray');
+    typed = require('./typed');
+    utility = require('./utility');
+
+    function sliceSource(source, index, last) {
+        return source.slice(index, last);
+    }
+
+    hasOwnProperty = (function () {
+        var func = Object.prototype.hasOwnProperty;
+        return function hasOwnProperty(obj, name) {
+            return func.call(obj, name);
+        };
+    }());
+
+    function shallowCopy(obj) {
+        var ret = {}, key;
+        for (key in obj) {
+            if (obj.hasOwnProperty(key)) {
+                ret[key] = obj[key];
+            }
+        }
+        return ret;
+    }
+
+    function isASCIIAlphanumeric(ch) {
+        return (ch >= 0x61  /* 'a' */ && ch <= 0x7A  /* 'z' */) ||
+            (ch >= 0x41  /* 'A' */ && ch <= 0x5A  /* 'Z' */) ||
+            (ch >= 0x30  /* '0' */ && ch <= 0x39  /* '9' */);
+    }
+
+    function isParamTitle(title) {
+        return title === 'param' || title === 'argument' || title === 'arg';
+    }
+
+    function isReturnTitle(title) {
+        return title === 'return' || title === 'returns';
+    }
+
+    function isProperty(title) {
+        return title === 'property' || title === 'prop';
+    }
+
+    function isNameParameterRequired(title) {
+        return isParamTitle(title) || isProperty(title) ||
+            title === 'alias' || title === 'this' || title === 'mixes' || title === 'requires';
+    }
+
+    function isAllowedName(title) {
+        return isNameParameterRequired(title) || title === 'const' || title === 'constant';
+    }
+
+    function isAllowedNested(title) {
+        return isProperty(title) || isParamTitle(title);
+    }
+
+    function isAllowedOptional(title) {
+        return isProperty(title) || isParamTitle(title);
+    }
+
+    function isTypeParameterRequired(title) {
+        return isParamTitle(title) || isReturnTitle(title) ||
+            title === 'define' || title === 'enum' ||
+            title === 'implements' || title === 'this' ||
+            title === 'type' || title === 'typedef' || isProperty(title);
+    }
+
+    // Consider deprecation instead using 'isTypeParameterRequired' and 'Rules' declaration to pick when a type is optional/required
+    // This would require changes to 'parseType'
+    function isAllowedType(title) {
+        return isTypeParameterRequired(title) || title === 'throws' || title === 'const' || title === 'constant' ||
+            title === 'namespace' || title === 'member' || title === 'var' || title === 'module' ||
+            title === 'constructor' || title === 'class' || title === 'extends' || title === 'augments' ||
+            title === 'public' || title === 'private' || title === 'protected';
+    }
+
+    function trim(str) {
+        return str.replace(/^\s+/, '').replace(/\s+$/, '');
+    }
+
+    function unwrapComment(doc) {
+        // JSDoc comment is following form
+        //   /**
+        //    * .......
+        //    */
+        // remove /**, */ and *
+        var BEFORE_STAR = 0,
+            STAR = 1,
+            AFTER_STAR = 2,
+            index,
+            len,
+            mode,
+            result,
+            ch;
+
+        doc = doc.replace(/^\/\*\*?/, '').replace(/\*\/$/, '');
+        index = 0;
+        len = doc.length;
+        mode = BEFORE_STAR;
+        result = '';
+
+        while (index < len) {
+            ch = doc.charCodeAt(index);
+            switch (mode) {
+            case BEFORE_STAR:
+                if (esutils.code.isLineTerminator(ch)) {
+                    result += String.fromCharCode(ch);
+                } else if (ch === 0x2A  /* '*' */) {
+                    mode = STAR;
+                } else if (!esutils.code.isWhiteSpace(ch)) {
+                    result += String.fromCharCode(ch);
+                    mode = AFTER_STAR;
+                }
+                break;
+
+            case STAR:
+                if (!esutils.code.isWhiteSpace(ch)) {
+                    result += String.fromCharCode(ch);
+                }
+                mode = esutils.code.isLineTerminator(ch) ? BEFORE_STAR : AFTER_STAR;
+                break;
+
+            case AFTER_STAR:
+                result += String.fromCharCode(ch);
+                if (esutils.code.isLineTerminator(ch)) {
+                    mode = BEFORE_STAR;
+                }
+                break;
+            }
+            index += 1;
+        }
+
+        return result.replace(/\s+$/, '');
+    }
+
+    // JSDoc Tag Parser
+
+    (function (exports) {
+        var Rules,
+            index,
+            lineNumber,
+            length,
+            source,
+            recoverable,
+            sloppy,
+            strict;
+
+        function advance() {
+            var ch = source.charCodeAt(index);
+            index += 1;
+            if (esutils.code.isLineTerminator(ch) && !(ch === 0x0D  /* '\r' */ && source.charCodeAt(index) === 0x0A  /* '\n' */)) {
+                lineNumber += 1;
+            }
+            return String.fromCharCode(ch);
+        }
+
+        function scanTitle() {
+            var title = '';
+            // waste '@'
+            advance();
+
+            while (index < length && isASCIIAlphanumeric(source.charCodeAt(index))) {
+                title += advance();
+            }
+
+            return title;
+        }
+
+        function seekContent() {
+            var ch, waiting, last = index;
+
+            waiting = false;
+            while (last < length) {
+                ch = source.charCodeAt(last);
+                if (esutils.code.isLineTerminator(ch) && !(ch === 0x0D  /* '\r' */ && source.charCodeAt(last + 1) === 0x0A  /* '\n' */)) {
+                    waiting = true;
+                } else if (waiting) {
+                    if (ch === 0x40  /* '@' */) {
+                        break;
+                    }
+                    if (!esutils.code.isWhiteSpace(ch)) {
+                        waiting = false;
+                    }
+                }
+                last += 1;
+            }
+            return last;
+        }
+
+        // type expression may have nest brace, such as,
+        // { { ok: string } }
+        //
+        // therefore, scanning type expression with balancing braces.
+        function parseType(title, last) {
+            var ch, brace, type, direct = false;
+
+
+            // search '{'
+            while (index < last) {
+                ch = source.charCodeAt(index);
+                if (esutils.code.isWhiteSpace(ch)) {
+                    advance();
+                } else if (ch === 0x7B  /* '{' */) {
+                    advance();
+                    break;
+                } else {
+                    // this is direct pattern
+                    direct = true;
+                    break;
+                }
+            }
+
+
+            if (direct) {
+                return null;
+            }
+
+            // type expression { is found
+            brace = 1;
+            type = '';
+            while (index < last) {
+                ch = source.charCodeAt(index);
+                if (esutils.code.isLineTerminator(ch)) {
+                    advance();
+                } else {
+                    if (ch === 0x7D  /* '}' */) {
+                        brace -= 1;
+                        if (brace === 0) {
+                            advance();
+                            break;
+                        }
+                    } else if (ch === 0x7B  /* '{' */) {
+                        brace += 1;
+                    }
+                    type += advance();
+                }
+            }
+
+            if (brace !== 0) {
+                // braces is not balanced
+                return utility.throwError('Braces are not balanced');
+            }
+
+            if (isAllowedOptional(title)) {
+                return typed.parseParamType(type);
+            }
+
+            return typed.parseType(type);
+        }
+
+        function scanIdentifier(last) {
+            var identifier;
+            if (!esutils.code.isIdentifierStartES5(source.charCodeAt(index))) {
+                return null;
+            }
+            identifier = advance();
+            while (index < last && esutils.code.isIdentifierPartES5(source.charCodeAt(index))) {
+                identifier += advance();
+            }
+            return identifier;
+        }
+
+        function skipWhiteSpace(last) {
+            while (index < last && (esutils.code.isWhiteSpace(source.charCodeAt(index)) || esutils.code.isLineTerminator(source.charCodeAt(index)))) {
+                advance();
+            }
+        }
+
+        function parseName(last, allowBrackets, allowNestedParams) {
+            var name = '',
+                useBrackets,
+                insideString;
+
+
+            skipWhiteSpace(last);
+
+            if (index >= last) {
+                return null;
+            }
+
+            if (allowBrackets && source.charCodeAt(index) === 0x5B  /* '[' */) {
+                useBrackets = true;
+                name = advance();
+            }
+
+            if (!esutils.code.isIdentifierStartES5(source.charCodeAt(index))) {
+                return null;
+            }
+
+            name += scanIdentifier(last);
+
+            if (allowNestedParams) {
+                if (source.charCodeAt(index) === 0x3A /* ':' */ && (
+                        name === 'module' ||
+                        name === 'external' ||
+                        name === 'event')) {
+                    name += advance();
+                    name += scanIdentifier(last);
+
+                }
+                if(source.charCodeAt(index) === 0x5B  /* '[' */ && source.charCodeAt(index + 1) === 0x5D  /* ']' */){
+                    name += advance();
+                    name += advance();
+                }
+                while (source.charCodeAt(index) === 0x2E  /* '.' */ ||
+                        source.charCodeAt(index) === 0x2F  /* '/' */ ||
+                        source.charCodeAt(index) === 0x23  /* '#' */ ||
+                        source.charCodeAt(index) === 0x2D  /* '-' */ ||
+                        source.charCodeAt(index) === 0x7E  /* '~' */) {
+                    name += advance();
+                    name += scanIdentifier(last);
+                }
+            }
+
+            if (useBrackets) {
+                skipWhiteSpace(last);
+                // do we have a default value for this?
+                if (source.charCodeAt(index) === 0x3D  /* '=' */) {
+                    // consume the '='' symbol
+                    name += advance();
+                    skipWhiteSpace(last);
+
+                    var ch;
+                    var bracketDepth = 1;
+
+                    // scan in the default value
+                    while (index < last) {
+                        ch = source.charCodeAt(index);
+
+                        if (esutils.code.isWhiteSpace(ch)) {
+                            if (!insideString) {
+                                skipWhiteSpace(last);
+                                ch = source.charCodeAt(index);
+                            }
+                        }
+
+                        if (ch === 0x27 /* ''' */) {
+                            if (!insideString) {
+                                insideString = '\'';
+                            } else {
+                                if (insideString === '\'') {
+                                    insideString = '';
+                                }
+                            }
+                        }
+
+                        if (ch === 0x22 /* '"' */) {
+                            if (!insideString) {
+                                insideString = '"';
+                            } else {
+                                if (insideString === '"') {
+                                    insideString = '';
+                                }
+                            }
+                        }
+
+                        if (ch === 0x5B /* '[' */) {
+                            bracketDepth++;
+                        } else if (ch === 0x5D  /* ']' */ &&
+                            --bracketDepth === 0) {
+                            break;
+                        }
+
+                        name += advance();
+                    }
+                }
+
+                skipWhiteSpace(last);
+
+                if (index >= last || source.charCodeAt(index) !== 0x5D  /* ']' */) {
+                    // we never found a closing ']'
+                    return null;
+                }
+
+                // collect the last ']'
+                name += advance();
+            }
+
+            return name;
+        }
+
+        function skipToTag() {
+            while (index < length && source.charCodeAt(index) !== 0x40  /* '@' */) {
+                advance();
+            }
+            if (index >= length) {
+                return false;
+            }
+            utility.assert(source.charCodeAt(index) === 0x40  /* '@' */);
+            return true;
+        }
+
+        function TagParser(options, title) {
+            this._options = options;
+            this._title = title.toLowerCase();
+            this._tag = {
+                title: title,
+                description: null
+            };
+            if (this._options.lineNumbers) {
+                this._tag.lineNumber = lineNumber;
+            }
+            this._last = 0;
+            // space to save special information for title parsers.
+            this._extra = { };
+        }
+
+        // addError(err, ...)
+        TagParser.prototype.addError = function addError(errorText) {
+            var args = Array.prototype.slice.call(arguments, 1),
+                msg = errorText.replace(
+                    /%(\d)/g,
+                    function (whole, index) {
+                        utility.assert(index < args.length, 'Message reference must be in range');
+                        return args[index];
+                    }
+                );
+
+            if (!this._tag.errors) {
+                this._tag.errors = [];
+            }
+            if (strict) {
+                utility.throwError(msg);
+            }
+            this._tag.errors.push(msg);
+            return recoverable;
+        };
+
+        TagParser.prototype.parseType = function () {
+            // type required titles
+            if (isTypeParameterRequired(this._title)) {
+                try {
+                    this._tag.type = parseType(this._title, this._last);
+                    if (!this._tag.type) {
+                        if (!isParamTitle(this._title) && !isReturnTitle(this._title)) {
+                            if (!this.addError('Missing or invalid tag type')) {
+                                return false;
+                            }
+                        }
+                    }
+                } catch (error) {
+                    this._tag.type = null;
+                    if (!this.addError(error.message)) {
+                        return false;
+                    }
+                }
+            } else if (isAllowedType(this._title)) {
+                // optional types
+                try {
+                    this._tag.type = parseType(this._title, this._last);
+                } catch (e) {
+                    //For optional types, lets drop the thrown error when we hit the end of the file
+                }
+            }
+            return true;
+        };
+
+        TagParser.prototype._parseNamePath = function (optional) {
+            var name;
+            name = parseName(this._last, sloppy && isAllowedOptional(this._title), true);
+            if (!name) {
+                if (!optional) {
+                    if (!this.addError('Missing or invalid tag name')) {
+                        return false;
+                    }
+                }
+            }
+            this._tag.name = name;
+            return true;
+        };
+
+        TagParser.prototype.parseNamePath = function () {
+            return this._parseNamePath(false);
+        };
+
+        TagParser.prototype.parseNamePathOptional = function () {
+            return this._parseNamePath(true);
+        };
+
+
+        TagParser.prototype.parseName = function () {
+            var assign, name;
+
+            // param, property requires name
+            if (isAllowedName(this._title)) {
+                this._tag.name = parseName(this._last, sloppy && isAllowedOptional(this._title), isAllowedNested(this._title));
+                if (!this._tag.name) {
+                    if (!isNameParameterRequired(this._title)) {
+                        return true;
+                    }
+
+                    // it's possible the name has already been parsed but interpreted as a type
+                    // it's also possible this is a sloppy declaration, in which case it will be
+                    // fixed at the end
+                    if (isParamTitle(this._title) && this._tag.type && this._tag.type.name) {
+                        this._extra.name = this._tag.type;
+                        this._tag.name = this._tag.type.name;
+                        this._tag.type = null;
+                    } else {
+                        if (!this.addError('Missing or invalid tag name')) {
+                            return false;
+                        }
+                    }
+                } else {
+                    name = this._tag.name;
+                    if (name.charAt(0) === '[' && name.charAt(name.length - 1) === ']') {
+                        // extract the default value if there is one
+                        // example: @param {string} [somebody=John Doe] description
+                        assign = name.substring(1, name.length - 1).split('=');
+                        if (assign[1]) {
+                            this._tag['default'] = assign[1];
+                        }
+                        this._tag.name = assign[0];
+
+                        // convert to an optional type
+                        if (this._tag.type && this._tag.type.type !== 'OptionalType') {
+                            this._tag.type = {
+                                type: 'OptionalType',
+                                expression: this._tag.type
+                            };
+                        }
+                    }
+                }
+            }
+
+
+            return true;
+        };
+
+        TagParser.prototype.parseDescription = function parseDescription() {
+            var description = trim(sliceSource(source, index, this._last));
+            if (description) {
+                if ((/^-\s+/).test(description)) {
+                    description = description.substring(2);
+                }
+                this._tag.description = description;
+            }
+            return true;
+        };
+
+        TagParser.prototype.parseCaption = function parseDescription() {
+            var description = trim(sliceSource(source, index, this._last));
+            var captionStartTag = '<caption>';
+            var captionEndTag = '</caption>';
+            var captionStart = description.indexOf(captionStartTag);
+            var captionEnd = description.indexOf(captionEndTag);
+            if (captionStart >= 0 && captionEnd >= 0) {
+                this._tag.caption = trim(description.substring(
+                    captionStart + captionStartTag.length, captionEnd));
+                this._tag.description = trim(description.substring(captionEnd + captionEndTag.length));
+            } else {
+                this._tag.description = description;
+            }
+            return true;
+        };
+
+        TagParser.prototype.parseKind = function parseKind() {
+            var kind, kinds;
+            kinds = {
+                'class': true,
+                'constant': true,
+                'event': true,
+                'external': true,
+                'file': true,
+                'function': true,
+                'member': true,
+                'mixin': true,
+                'module': true,
+                'namespace': true,
+                'typedef': true
+            };
+            kind = trim(sliceSource(source, index, this._last));
+            this._tag.kind = kind;
+            if (!hasOwnProperty(kinds, kind)) {
+                if (!this.addError('Invalid kind name \'%0\'', kind)) {
+                    return false;
+                }
+            }
+            return true;
+        };
+
+        TagParser.prototype.parseAccess = function parseAccess() {
+            var access;
+            access = trim(sliceSource(source, index, this._last));
+            this._tag.access = access;
+            if (access !== 'private' && access !== 'protected' && access !== 'public') {
+                if (!this.addError('Invalid access name \'%0\'', access)) {
+                    return false;
+                }
+            }
+            return true;
+        };
+
+        TagParser.prototype.parseThis = function parseAccess() {
+            // this name may be a name expression (e.g. {foo.bar})
+            // or a name path (e.g. foo.bar)
+            var value = trim(sliceSource(source, index, this._last));
+            if (value && value.charAt(0) === '{') {
+                var gotType = this.parseType();
+                if (gotType && this._tag.type.type === 'NameExpression') {
+                    this._tag.name = this._tag.type.name;
+                    return true;
+                } else {
+                    return this.addError('Invalid name for this');
+                }
+            } else {
+                return this.parseNamePath();
+            }
+        };
+
+        TagParser.prototype.parseVariation = function parseVariation() {
+            var variation, text;
+            text = trim(sliceSource(source, index, this._last));
+            variation = parseFloat(text, 10);
+            this._tag.variation = variation;
+            if (isNaN(variation)) {
+                if (!this.addError('Invalid variation \'%0\'', text)) {
+                    return false;
+                }
+            }
+            return true;
+        };
+
+        TagParser.prototype.ensureEnd = function () {
+            var shouldBeEmpty = trim(sliceSource(source, index, this._last));
+            if (shouldBeEmpty) {
+                if (!this.addError('Unknown content \'%0\'', shouldBeEmpty)) {
+                    return false;
+                }
+            }
+            return true;
+        };
+
+        TagParser.prototype.epilogue = function epilogue() {
+            var description;
+
+            description = this._tag.description;
+            // un-fix potentially sloppy declaration
+            if (isAllowedOptional(this._title) && !this._tag.type && description && description.charAt(0) === '[') {
+                this._tag.type = this._extra.name;
+                if (!this._tag.name) {
+                    this._tag.name = undefined;
+                }
+
+                if (!sloppy) {
+                    if (!this.addError('Missing or invalid tag name')) {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        };
+
+        Rules = {
+            // http://usejsdoc.org/tags-access.html
+            'access': ['parseAccess'],
+            // http://usejsdoc.org/tags-alias.html
+            'alias': ['parseNamePath', 'ensureEnd'],
+            // http://usejsdoc.org/tags-augments.html
+            'augments': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
+            // http://usejsdoc.org/tags-constructor.html
+            'constructor': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
+            // Synonym: http://usejsdoc.org/tags-constructor.html
+            'class': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
+            // Synonym: http://usejsdoc.org/tags-extends.html
+            'extends': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
+            // http://usejsdoc.org/tags-example.html
+            'example': ['parseCaption'],
+            // http://usejsdoc.org/tags-deprecated.html
+            'deprecated': ['parseDescription'],
+            // http://usejsdoc.org/tags-global.html
+            'global': ['ensureEnd'],
+            // http://usejsdoc.org/tags-inner.html
+            'inner': ['ensureEnd'],
+            // http://usejsdoc.org/tags-instance.html
+            'instance': ['ensureEnd'],
+            // http://usejsdoc.org/tags-kind.html
+            'kind': ['parseKind'],
+            // http://usejsdoc.org/tags-mixes.html
+            'mixes': ['parseNamePath', 'ensureEnd'],
+            // http://usejsdoc.org/tags-mixin.html
+            'mixin': ['parseNamePathOptional', 'ensureEnd'],
+            // http://usejsdoc.org/tags-member.html
+            'member': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
+            // http://usejsdoc.org/tags-method.html
+            'method': ['parseNamePathOptional', 'ensureEnd'],
+            // http://usejsdoc.org/tags-module.html
+            'module': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
+            // Synonym: http://usejsdoc.org/tags-method.html
+            'func': ['parseNamePathOptional', 'ensureEnd'],
+            // Synonym: http://usejsdoc.org/tags-method.html
+            'function': ['parseNamePathOptional', 'ensureEnd'],
+            // Synonym: http://usejsdoc.org/tags-member.html
+            'var': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
+            // http://usejsdoc.org/tags-name.html
+            'name': ['parseNamePath', 'ensureEnd'],
+            // http://usejsdoc.org/tags-namespace.html
+            'namespace': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
+            // http://usejsdoc.org/tags-private.html
+            'private': ['parseType', 'parseDescription'],
+            // http://usejsdoc.org/tags-protected.html
+            'protected': ['parseType', 'parseDescription'],
+            // http://usejsdoc.org/tags-public.html
+            'public': ['parseType', 'parseDescription'],
+            // http://usejsdoc.org/tags-readonly.html
+            'readonly': ['ensureEnd'],
+            // http://usejsdoc.org/tags-requires.html
+            'requires': ['parseNamePath', 'ensureEnd'],
+            // http://usejsdoc.org/tags-since.html
+            'since': ['parseDescription'],
+            // http://usejsdoc.org/tags-static.html
+            'static': ['ensureEnd'],
+            // http://usejsdoc.org/tags-summary.html
+            'summary': ['parseDescription'],
+            // http://usejsdoc.org/tags-this.html
+            'this': ['parseThis', 'ensureEnd'],
+            // http://usejsdoc.org/tags-todo.html
+            'todo': ['parseDescription'],
+            // http://usejsdoc.org/tags-typedef.html
+            'typedef': ['parseType', 'parseNamePathOptional'],
+            // http://usejsdoc.org/tags-variation.html
+            'variation': ['parseVariation'],
+            // http://usejsdoc.org/tags-version.html
+            'version': ['parseDescription']
+        };
+
+        TagParser.prototype.parse = function parse() {
+            var i, iz, sequences, method;
+
+
+            // empty title
+            if (!this._title) {
+                if (!this.addError('Missing or invalid title')) {
+                    return null;
+                }
+            }
+
+            // Seek to content last index.
+            this._last = seekContent(this._title);
+
+            if (hasOwnProperty(Rules, this._title)) {
+                sequences = Rules[this._title];
+            } else {
+                // default sequences
+                sequences = ['parseType', 'parseName', 'parseDescription', 'epilogue'];
+            }
+
+            for (i = 0, iz = sequences.length; i < iz; ++i) {
+                method = sequences[i];
+                if (!this[method]()) {
+                    return null;
+                }
+            }
+
+            return this._tag;
+        };
+
+        function parseTag(options) {
+            var title, parser, tag;
+
+            // skip to tag
+            if (!skipToTag()) {
+                return null;
+            }
+
+            // scan title
+            title = scanTitle();
+
+            // construct tag parser
+            parser = new TagParser(options, title);
+            tag = parser.parse();
+
+            // Seek global index to end of this tag.
+            while (index < parser._last) {
+                advance();
+            }
+
+            return tag;
+        }
+
+        //
+        // Parse JSDoc
+        //
+
+        function scanJSDocDescription(preserveWhitespace) {
+            var description = '', ch, atAllowed;
+
+            atAllowed = true;
+            while (index < length) {
+                ch = source.charCodeAt(index);
+
+                if (atAllowed && ch === 0x40  /* '@' */) {
+                    break;
+                }
+
+                if (esutils.code.isLineTerminator(ch)) {
+                    atAllowed = true;
+                } else if (atAllowed && !esutils.code.isWhiteSpace(ch)) {
+                    atAllowed = false;
+                }
+
+                description += advance();
+            }
+
+            return preserveWhitespace ? description : trim(description);
+        }
+
+        function parse(comment, options) {
+            var tags = [], tag, description, interestingTags, i, iz;
+
+            if (options === undefined) {
+                options = {};
+            }
+
+            if (typeof options.unwrap === 'boolean' && options.unwrap) {
+                source = unwrapComment(comment);
+            } else {
+                source = comment;
+            }
+
+            // array of relevant tags
+            if (options.tags) {
+                if (isArray(options.tags)) {
+                    interestingTags = { };
+                    for (i = 0, iz = options.tags.length; i < iz; i++) {
+                        if (typeof options.tags[i] === 'string') {
+                            interestingTags[options.tags[i]] = true;
+                        } else {
+                            utility.throwError('Invalid "tags" parameter: ' + options.tags);
+                        }
+                    }
+                } else {
+                    utility.throwError('Invalid "tags" parameter: ' + options.tags);
+                }
+            }
+
+            length = source.length;
+            index = 0;
+            lineNumber = 0;
+            recoverable = options.recoverable;
+            sloppy = options.sloppy;
+            strict = options.strict;
+
+            description = scanJSDocDescription(options.preserveWhitespace);
+
+            while (true) {
+                tag = parseTag(options);
+                if (!tag) {
+                    break;
+                }
+                if (!interestingTags || interestingTags.hasOwnProperty(tag.title)) {
+                    tags.push(tag);
+                }
+            }
+
+            return {
+                description: description,
+                tags: tags
+            };
+        }
+        exports.parse = parse;
+    }(jsdoc = {}));
+
+    exports.version = utility.VERSION;
+    exports.parse = jsdoc.parse;
+    exports.parseType = typed.parseType;
+    exports.parseParamType = typed.parseParamType;
+    exports.unwrapComment = unwrapComment;
+    exports.Syntax = shallowCopy(typed.Syntax);
+    exports.Error = utility.DoctrineError;
+    exports.type = {
+        Syntax: exports.Syntax,
+        parseType: typed.parseType,
+        parseParamType: typed.parseParamType,
+        stringify: typed.stringify
+    };
+}());
+/* vim: set sw=4 ts=4 et tw=80 : */
diff --git a/lib/typed.js b/lib/typed.js
new file mode 100644
index 0000000..f0d759b
--- /dev/null
+++ b/lib/typed.js
@@ -0,0 +1,1283 @@
+/*
+ * @fileoverview Type expression parser.
+ * @author Yusuke Suzuki <utatane.tea at gmail.com>
+ * @author Dan Tao <daniel.tao at gmail.com>
+ * @author Andrew Eisenberg <andrew at eisenberg.as>
+ */
+
+// "typed", the Type Expression Parser for doctrine.
+
+(function () {
+    'use strict';
+
+    var Syntax,
+        Token,
+        source,
+        length,
+        index,
+        previous,
+        token,
+        value,
+        esutils,
+        utility;
+
+    esutils = require('esutils');
+    utility = require('./utility');
+
+    Syntax = {
+        NullableLiteral: 'NullableLiteral',
+        AllLiteral: 'AllLiteral',
+        NullLiteral: 'NullLiteral',
+        UndefinedLiteral: 'UndefinedLiteral',
+        VoidLiteral: 'VoidLiteral',
+        UnionType: 'UnionType',
+        ArrayType: 'ArrayType',
+        RecordType: 'RecordType',
+        FieldType: 'FieldType',
+        FunctionType: 'FunctionType',
+        ParameterType: 'ParameterType',
+        RestType: 'RestType',
+        NonNullableType: 'NonNullableType',
+        OptionalType: 'OptionalType',
+        NullableType: 'NullableType',
+        NameExpression: 'NameExpression',
+        TypeApplication: 'TypeApplication',
+        StringLiteralType: 'StringLiteralType',
+        NumericLiteralType: 'NumericLiteralType',
+        BooleanLiteralType: 'BooleanLiteralType'
+    };
+
+    Token = {
+        ILLEGAL: 0,    // ILLEGAL
+        DOT_LT: 1,     // .<
+        REST: 2,       // ...
+        LT: 3,         // <
+        GT: 4,         // >
+        LPAREN: 5,     // (
+        RPAREN: 6,     // )
+        LBRACE: 7,     // {
+        RBRACE: 8,     // }
+        LBRACK: 9,    // [
+        RBRACK: 10,    // ]
+        COMMA: 11,     // ,
+        COLON: 12,     // :
+        STAR: 13,      // *
+        PIPE: 14,      // |
+        QUESTION: 15,  // ?
+        BANG: 16,      // !
+        EQUAL: 17,     // =
+        NAME: 18,      // name token
+        STRING: 19,    // string
+        NUMBER: 20,    // number
+        EOF: 21
+    };
+
+    function isTypeName(ch) {
+        return '><(){}[],:*|?!='.indexOf(String.fromCharCode(ch)) === -1 && !esutils.code.isWhiteSpace(ch) && !esutils.code.isLineTerminator(ch);
+    }
+
+    function Context(previous, index, token, value) {
+        this._previous = previous;
+        this._index = index;
+        this._token = token;
+        this._value = value;
+    }
+
+    Context.prototype.restore = function () {
+        previous = this._previous;
+        index = this._index;
+        token = this._token;
+        value = this._value;
+    };
+
+    Context.save = function () {
+        return new Context(previous, index, token, value);
+    };
+
+    function advance() {
+        var ch = source.charAt(index);
+        index += 1;
+        return ch;
+    }
+
+    function scanHexEscape(prefix) {
+        var i, len, ch, code = 0;
+
+        len = (prefix === 'u') ? 4 : 2;
+        for (i = 0; i < len; ++i) {
+            if (index < length && esutils.code.isHexDigit(source.charCodeAt(index))) {
+                ch = advance();
+                code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
+            } else {
+                return '';
+            }
+        }
+        return String.fromCharCode(code);
+    }
+
+    function scanString() {
+        var str = '', quote, ch, code, unescaped, restore; //TODO review removal octal = false
+        quote = source.charAt(index);
+        ++index;
+
+        while (index < length) {
+            ch = advance();
+
+            if (ch === quote) {
+                quote = '';
+                break;
+            } else if (ch === '\\') {
+                ch = advance();
+                if (!esutils.code.isLineTerminator(ch.charCodeAt(0))) {
+                    switch (ch) {
+                    case 'n':
+                        str += '\n';
+                        break;
+                    case 'r':
+                        str += '\r';
+                        break;
+                    case 't':
+                        str += '\t';
+                        break;
+                    case 'u':
+                    case 'x':
+                        restore = index;
+                        unescaped = scanHexEscape(ch);
+                        if (unescaped) {
+                            str += unescaped;
+                        } else {
+                            index = restore;
+                            str += ch;
+                        }
+                        break;
+                    case 'b':
+                        str += '\b';
+                        break;
+                    case 'f':
+                        str += '\f';
+                        break;
+                    case 'v':
+                        str += '\v';
+                        break;
+
+                    default:
+                        if (esutils.code.isOctalDigit(ch.charCodeAt(0))) {
+                            code = '01234567'.indexOf(ch);
+
+                            // \0 is not octal escape sequence
+                            // Deprecating unused code. TODO review removal
+                            //if (code !== 0) {
+                            //    octal = true;
+                            //}
+
+                            if (index < length && esutils.code.isOctalDigit(source.charCodeAt(index))) {
+                                //TODO Review Removal octal = true;
+                                code = code * 8 + '01234567'.indexOf(advance());
+
+                                // 3 digits are only allowed when string starts
+                                // with 0, 1, 2, 3
+                                if ('0123'.indexOf(ch) >= 0 &&
+                                        index < length &&
+                                        esutils.code.isOctalDigit(source.charCodeAt(index))) {
+                                    code = code * 8 + '01234567'.indexOf(advance());
+                                }
+                            }
+                            str += String.fromCharCode(code);
+                        } else {
+                            str += ch;
+                        }
+                        break;
+                    }
+                } else {
+                    if (ch ===  '\r' && source.charCodeAt(index) === 0x0A  /* '\n' */) {
+                        ++index;
+                    }
+                }
+            } else if (esutils.code.isLineTerminator(ch.charCodeAt(0))) {
+                break;
+            } else {
+                str += ch;
+            }
+        }
+
+        if (quote !== '') {
+            utility.throwError('unexpected quote');
+        }
+
+        value = str;
+        return Token.STRING;
+    }
+
+    function scanNumber() {
+        var number, ch;
+
+        number = '';
+        ch = source.charCodeAt(index);
+
+        if (ch !== 0x2E  /* '.' */) {
+            number = advance();
+            ch = source.charCodeAt(index);
+
+            if (number === '0') {
+                if (ch === 0x78  /* 'x' */ || ch === 0x58  /* 'X' */) {
+                    number += advance();
+                    while (index < length) {
+                        ch = source.charCodeAt(index);
+                        if (!esutils.code.isHexDigit(ch)) {
+                            break;
+                        }
+                        number += advance();
+                    }
+
+                    if (number.length <= 2) {
+                        // only 0x
+                        utility.throwError('unexpected token');
+                    }
+
+                    if (index < length) {
+                        ch = source.charCodeAt(index);
+                        if (esutils.code.isIdentifierStartES5(ch)) {
+                            utility.throwError('unexpected token');
+                        }
+                    }
+                    value = parseInt(number, 16);
+                    return Token.NUMBER;
+                }
+
+                if (esutils.code.isOctalDigit(ch)) {
+                    number += advance();
+                    while (index < length) {
+                        ch = source.charCodeAt(index);
+                        if (!esutils.code.isOctalDigit(ch)) {
+                            break;
+                        }
+                        number += advance();
+                    }
+
+                    if (index < length) {
+                        ch = source.charCodeAt(index);
+                        if (esutils.code.isIdentifierStartES5(ch) || esutils.code.isDecimalDigit(ch)) {
+                            utility.throwError('unexpected token');
+                        }
+                    }
+                    value = parseInt(number, 8);
+                    return Token.NUMBER;
+                }
+
+                if (esutils.code.isDecimalDigit(ch)) {
+                    utility.throwError('unexpected token');
+                }
+            }
+
+            while (index < length) {
+                ch = source.charCodeAt(index);
+                if (!esutils.code.isDecimalDigit(ch)) {
+                    break;
+                }
+                number += advance();
+            }
+        }
+
+        if (ch === 0x2E  /* '.' */) {
+            number += advance();
+            while (index < length) {
+                ch = source.charCodeAt(index);
+                if (!esutils.code.isDecimalDigit(ch)) {
+                    break;
+                }
+                number += advance();
+            }
+        }
+
+        if (ch === 0x65  /* 'e' */ || ch === 0x45  /* 'E' */) {
+            number += advance();
+
+            ch = source.charCodeAt(index);
+            if (ch === 0x2B  /* '+' */ || ch === 0x2D  /* '-' */) {
+                number += advance();
+            }
+
+            ch = source.charCodeAt(index);
+            if (esutils.code.isDecimalDigit(ch)) {
+                number += advance();
+                while (index < length) {
+                    ch = source.charCodeAt(index);
+                    if (!esutils.code.isDecimalDigit(ch)) {
+                        break;
+                    }
+                    number += advance();
+                }
+            } else {
+                utility.throwError('unexpected token');
+            }
+        }
+
+        if (index < length) {
+            ch = source.charCodeAt(index);
+            if (esutils.code.isIdentifierStartES5(ch)) {
+                utility.throwError('unexpected token');
+            }
+        }
+
+        value = parseFloat(number);
+        return Token.NUMBER;
+    }
+
+
+    function scanTypeName() {
+        var ch, ch2;
+
+        value = advance();
+        while (index < length && isTypeName(source.charCodeAt(index))) {
+            ch = source.charCodeAt(index);
+            if (ch === 0x2E  /* '.' */) {
+                if ((index + 1) >= length) {
+                    return Token.ILLEGAL;
+                }
+                ch2 = source.charCodeAt(index + 1);
+                if (ch2 === 0x3C  /* '<' */) {
+                    break;
+                }
+            }
+            value += advance();
+        }
+        return Token.NAME;
+    }
+
+    function next() {
+        var ch;
+
+        previous = index;
+
+        while (index < length && esutils.code.isWhiteSpace(source.charCodeAt(index))) {
+            advance();
+        }
+        if (index >= length) {
+            token = Token.EOF;
+            return token;
+        }
+
+        ch = source.charCodeAt(index);
+        switch (ch) {
+        case 0x27:  /* ''' */
+        case 0x22:  /* '"' */
+            token = scanString();
+            return token;
+
+        case 0x3A:  /* ':' */
+            advance();
+            token = Token.COLON;
+            return token;
+
+        case 0x2C:  /* ',' */
+            advance();
+            token = Token.COMMA;
+            return token;
+
+        case 0x28:  /* '(' */
+            advance();
+            token = Token.LPAREN;
+            return token;
+
+        case 0x29:  /* ')' */
+            advance();
+            token = Token.RPAREN;
+            return token;
+
+        case 0x5B:  /* '[' */
+            advance();
+            token = Token.LBRACK;
+            return token;
+
+        case 0x5D:  /* ']' */
+            advance();
+            token = Token.RBRACK;
+            return token;
+
+        case 0x7B:  /* '{' */
+            advance();
+            token = Token.LBRACE;
+            return token;
+
+        case 0x7D:  /* '}' */
+            advance();
+            token = Token.RBRACE;
+            return token;
+
+        case 0x2E:  /* '.' */
+            if (index + 1 < length) {
+                ch = source.charCodeAt(index + 1);
+                if (ch === 0x3C  /* '<' */) {
+                    advance();  // '.'
+                    advance();  // '<'
+                    token = Token.DOT_LT;
+                    return token;
+                }
+
+                if (ch === 0x2E  /* '.' */ && index + 2 < length && source.charCodeAt(index + 2) === 0x2E  /* '.' */) {
+                    advance();  // '.'
+                    advance();  // '.'
+                    advance();  // '.'
+                    token = Token.REST;
+                    return token;
+                }
+
+                if (esutils.code.isDecimalDigit(ch)) {
+                    token = scanNumber();
+                    return token;
+                }
+            }
+            token = Token.ILLEGAL;
+            return token;
+
+        case 0x3C:  /* '<' */
+            advance();
+            token = Token.LT;
+            return token;
+
+        case 0x3E:  /* '>' */
+            advance();
+            token = Token.GT;
+            return token;
+
+        case 0x2A:  /* '*' */
+            advance();
+            token = Token.STAR;
+            return token;
+
+        case 0x7C:  /* '|' */
+            advance();
+            token = Token.PIPE;
+            return token;
+
+        case 0x3F:  /* '?' */
+            advance();
+            token = Token.QUESTION;
+            return token;
+
+        case 0x21:  /* '!' */
+            advance();
+            token = Token.BANG;
+            return token;
+
+        case 0x3D:  /* '=' */
+            advance();
+            token = Token.EQUAL;
+            return token;
+
+        case 0x2D: /* '-' */
+            token = scanNumber();
+            return token;
+
+        default:
+            if (esutils.code.isDecimalDigit(ch)) {
+                token = scanNumber();
+                return token;
+            }
+
+            // type string permits following case,
+            //
+            // namespace.module.MyClass
+            //
+            // this reduced 1 token TK_NAME
+            utility.assert(isTypeName(ch));
+            token = scanTypeName();
+            return token;
+        }
+    }
+
+    function consume(target, text) {
+        utility.assert(token === target, text || 'consumed token not matched');
+        next();
+    }
+
+    function expect(target, message) {
+        if (token !== target) {
+            utility.throwError(message || 'unexpected token');
+        }
+        next();
+    }
+
+    // UnionType := '(' TypeUnionList ')'
+    //
+    // TypeUnionList :=
+    //     <<empty>>
+    //   | NonemptyTypeUnionList
+    //
+    // NonemptyTypeUnionList :=
+    //     TypeExpression
+    //   | TypeExpression '|' NonemptyTypeUnionList
+    function parseUnionType() {
+        var elements;
+        consume(Token.LPAREN, 'UnionType should start with (');
+        elements = [];
+        if (token !== Token.RPAREN) {
+            while (true) {
+                elements.push(parseTypeExpression());
+                if (token === Token.RPAREN) {
+                    break;
+                }
+                expect(Token.PIPE);
+            }
+        }
+        consume(Token.RPAREN, 'UnionType should end with )');
+        return {
+            type: Syntax.UnionType,
+            elements: elements
+        };
+    }
+
+    // ArrayType := '[' ElementTypeList ']'
+    //
+    // ElementTypeList :=
+    //     <<empty>>
+    //  | TypeExpression
+    //  | '...' TypeExpression
+    //  | TypeExpression ',' ElementTypeList
+    function parseArrayType() {
+        var elements;
+        consume(Token.LBRACK, 'ArrayType should start with [');
+        elements = [];
+        while (token !== Token.RBRACK) {
+            if (token === Token.REST) {
+                consume(Token.REST);
+                elements.push({
+                    type: Syntax.RestType,
+                    expression: parseTypeExpression()
+                });
+                break;
+            } else {
+                elements.push(parseTypeExpression());
+            }
+            if (token !== Token.RBRACK) {
+                expect(Token.COMMA);
+            }
+        }
+        expect(Token.RBRACK);
+        return {
+            type: Syntax.ArrayType,
+            elements: elements
+        };
+    }
+
+    function parseFieldName() {
+        var v = value;
+        if (token === Token.NAME || token === Token.STRING) {
+            next();
+            return v;
+        }
+
+        if (token === Token.NUMBER) {
+            consume(Token.NUMBER);
+            return String(v);
+        }
+
+        utility.throwError('unexpected token');
+    }
+
+    // FieldType :=
+    //     FieldName
+    //   | FieldName ':' TypeExpression
+    //
+    // FieldName :=
+    //     NameExpression
+    //   | StringLiteral
+    //   | NumberLiteral
+    //   | ReservedIdentifier
+    function parseFieldType() {
+        var key;
+
+        key = parseFieldName();
+        if (token === Token.COLON) {
+            consume(Token.COLON);
+            return {
+                type: Syntax.FieldType,
+                key: key,
+                value: parseTypeExpression()
+            };
+        }
+        return {
+            type: Syntax.FieldType,
+            key: key,
+            value: null
+        };
+    }
+
+    // RecordType := '{' FieldTypeList '}'
+    //
+    // FieldTypeList :=
+    //     <<empty>>
+    //   | FieldType
+    //   | FieldType ',' FieldTypeList
+    function parseRecordType() {
+        var fields;
+
+        consume(Token.LBRACE, 'RecordType should start with {');
+        fields = [];
+        if (token === Token.COMMA) {
+            consume(Token.COMMA);
+        } else {
+            while (token !== Token.RBRACE) {
+                fields.push(parseFieldType());
+                if (token !== Token.RBRACE) {
+                    expect(Token.COMMA);
+                }
+            }
+        }
+        expect(Token.RBRACE);
+        return {
+            type: Syntax.RecordType,
+            fields: fields
+        };
+    }
+
+    // NameExpression :=
+    //    Identifier
+    //  | TagIdentifier ':' Identifier
+    //
+    // Tag identifier is one of "module", "external" or "event"
+    // Identifier is the same as Token.NAME, including any dots, something like
+    // namespace.module.MyClass
+    function parseNameExpression() {
+        var name = value;
+        expect(Token.NAME);
+
+        if (token === Token.COLON && (
+                name === 'module' ||
+                name === 'external' ||
+                name === 'event')) {
+            consume(Token.COLON);
+            name += ':' + value;
+            expect(Token.NAME);
+        }
+
+        return {
+            type: Syntax.NameExpression,
+            name: name
+        };
+    }
+
+    // TypeExpressionList :=
+    //     TopLevelTypeExpression
+    //   | TopLevelTypeExpression ',' TypeExpressionList
+    function parseTypeExpressionList() {
+        var elements = [];
+
+        elements.push(parseTop());
+        while (token === Token.COMMA) {
+            consume(Token.COMMA);
+            elements.push(parseTop());
+        }
+        return elements;
+    }
+
+    // TypeName :=
+    //     NameExpression
+    //   | NameExpression TypeApplication
+    //
+    // TypeApplication :=
+    //     '.<' TypeExpressionList '>'
+    //   | '<' TypeExpressionList '>'   // this is extension of doctrine
+    function parseTypeName() {
+        var expr, applications;
+
+        expr = parseNameExpression();
+        if (token === Token.DOT_LT || token === Token.LT) {
+            next();
+            applications = parseTypeExpressionList();
+            expect(Token.GT);
+            return {
+                type: Syntax.TypeApplication,
+                expression: expr,
+                applications: applications
+            };
+        }
+        return expr;
+    }
+
+    // ResultType :=
+    //     <<empty>>
+    //   | ':' void
+    //   | ':' TypeExpression
+    //
+    // BNF is above
+    // but, we remove <<empty>> pattern, so token is always TypeToken::COLON
+    function parseResultType() {
+        consume(Token.COLON, 'ResultType should start with :');
+        if (token === Token.NAME && value === 'void') {
+            consume(Token.NAME);
+            return {
+                type: Syntax.VoidLiteral
+            };
+        }
+        return parseTypeExpression();
+    }
+
+    // ParametersType :=
+    //     RestParameterType
+    //   | NonRestParametersType
+    //   | NonRestParametersType ',' RestParameterType
+    //
+    // RestParameterType :=
+    //     '...'
+    //     '...' Identifier
+    //
+    // NonRestParametersType :=
+    //     ParameterType ',' NonRestParametersType
+    //   | ParameterType
+    //   | OptionalParametersType
+    //
+    // OptionalParametersType :=
+    //     OptionalParameterType
+    //   | OptionalParameterType, OptionalParametersType
+    //
+    // OptionalParameterType := ParameterType=
+    //
+    // ParameterType := TypeExpression | Identifier ':' TypeExpression
+    //
+    // Identifier is "new" or "this"
+    function parseParametersType() {
+        var params = [], optionalSequence = false, expr, rest = false;
+
+        while (token !== Token.RPAREN) {
+            if (token === Token.REST) {
+                // RestParameterType
+                consume(Token.REST);
+                rest = true;
+            }
+
+            expr = parseTypeExpression();
+            if (expr.type === Syntax.NameExpression && token === Token.COLON) {
+                // Identifier ':' TypeExpression
+                consume(Token.COLON);
+                expr = {
+                    type: Syntax.ParameterType,
+                    name: expr.name,
+                    expression: parseTypeExpression()
+                };
+            }
+            if (token === Token.EQUAL) {
+                consume(Token.EQUAL);
+                expr = {
+                    type: Syntax.OptionalType,
+                    expression: expr
+                };
+                optionalSequence = true;
+            } else {
+                if (optionalSequence) {
+                    utility.throwError('unexpected token');
+                }
+            }
+            if (rest) {
+                expr = {
+                    type: Syntax.RestType,
+                    expression: expr
+                };
+            }
+            params.push(expr);
+            if (token !== Token.RPAREN) {
+                expect(Token.COMMA);
+            }
+        }
+        return params;
+    }
+
+    // FunctionType := 'function' FunctionSignatureType
+    //
+    // FunctionSignatureType :=
+    //   | TypeParameters '(' ')' ResultType
+    //   | TypeParameters '(' ParametersType ')' ResultType
+    //   | TypeParameters '(' 'this' ':' TypeName ')' ResultType
+    //   | TypeParameters '(' 'this' ':' TypeName ',' ParametersType ')' ResultType
+    function parseFunctionType() {
+        var isNew, thisBinding, params, result, fnType;
+        utility.assert(token === Token.NAME && value === 'function', 'FunctionType should start with \'function\'');
+        consume(Token.NAME);
+
+        // Google Closure Compiler is not implementing TypeParameters.
+        // So we do not. if we don't get '(', we see it as error.
+        expect(Token.LPAREN);
+
+        isNew = false;
+        params = [];
+        thisBinding = null;
+        if (token !== Token.RPAREN) {
+            // ParametersType or 'this'
+            if (token === Token.NAME &&
+                    (value === 'this' || value === 'new')) {
+                // 'this' or 'new'
+                // 'new' is Closure Compiler extension
+                isNew = value === 'new';
+                consume(Token.NAME);
+                expect(Token.COLON);
+                thisBinding = parseTypeName();
+                if (token === Token.COMMA) {
+                    consume(Token.COMMA);
+                    params = parseParametersType();
+                }
+            } else {
+                params = parseParametersType();
+            }
+        }
+
+        expect(Token.RPAREN);
+
+        result = null;
+        if (token === Token.COLON) {
+            result = parseResultType();
+        }
+
+        fnType = {
+            type: Syntax.FunctionType,
+            params: params,
+            result: result
+        };
+        if (thisBinding) {
+            // avoid adding null 'new' and 'this' properties
+            fnType['this'] = thisBinding;
+            if (isNew) {
+                fnType['new'] = true;
+            }
+        }
+        return fnType;
+    }
+
+    // BasicTypeExpression :=
+    //     '*'
+    //   | 'null'
+    //   | 'undefined'
+    //   | TypeName
+    //   | FunctionType
+    //   | UnionType
+    //   | RecordType
+    //   | ArrayType
+    function parseBasicTypeExpression() {
+        var context;
+        switch (token) {
+        case Token.STAR:
+            consume(Token.STAR);
+            return {
+                type: Syntax.AllLiteral
+            };
+
+        case Token.LPAREN:
+            return parseUnionType();
+
+        case Token.LBRACK:
+            return parseArrayType();
+
+        case Token.LBRACE:
+            return parseRecordType();
+
+        case Token.NAME:
+            if (value === 'null') {
+                consume(Token.NAME);
+                return {
+                    type: Syntax.NullLiteral
+                };
+            }
+
+            if (value === 'undefined') {
+                consume(Token.NAME);
+                return {
+                    type: Syntax.UndefinedLiteral
+                };
+            }
+
+            if (value === 'true' || value === 'false') {
+                consume(Token.NAME);
+                return {
+                    type: Syntax.BooleanLiteralType,
+                    value: value === 'true'
+                };
+            }
+
+            context = Context.save();
+            if (value === 'function') {
+                try {
+                    return parseFunctionType();
+                } catch (e) {
+                    context.restore();
+                }
+            }
+
+            return parseTypeName();
+
+        case Token.STRING:
+            next();
+            return {
+                type: Syntax.StringLiteralType,
+                value: value
+            };
+
+        case Token.NUMBER:
+            next();
+            return {
+                type: Syntax.NumericLiteralType,
+                value: value
+            };
+
+        default:
+            utility.throwError('unexpected token');
+        }
+    }
+
+    // TypeExpression :=
+    //     BasicTypeExpression
+    //   | '?' BasicTypeExpression
+    //   | '!' BasicTypeExpression
+    //   | BasicTypeExpression '?'
+    //   | BasicTypeExpression '!'
+    //   | '?'
+    //   | BasicTypeExpression '[]'
+    function parseTypeExpression() {
+        var expr;
+
+        if (token === Token.QUESTION) {
+            consume(Token.QUESTION);
+            if (token === Token.COMMA || token === Token.EQUAL || token === Token.RBRACE ||
+                    token === Token.RPAREN || token === Token.PIPE || token === Token.EOF ||
+                    token === Token.RBRACK || token === Token.GT) {
+                return {
+                    type: Syntax.NullableLiteral
+                };
+            }
+            return {
+                type: Syntax.NullableType,
+                expression: parseBasicTypeExpression(),
+                prefix: true
+            };
+        }
+
+        if (token === Token.BANG) {
+            consume(Token.BANG);
+            return {
+                type: Syntax.NonNullableType,
+                expression: parseBasicTypeExpression(),
+                prefix: true
+            };
+        }
+
+        expr = parseBasicTypeExpression();
+        if (token === Token.BANG) {
+            consume(Token.BANG);
+            return {
+                type: Syntax.NonNullableType,
+                expression: expr,
+                prefix: false
+            };
+        }
+
+        if (token === Token.QUESTION) {
+            consume(Token.QUESTION);
+            return {
+                type: Syntax.NullableType,
+                expression: expr,
+                prefix: false
+            };
+        }
+
+        if (token === Token.LBRACK) {
+            consume(Token.LBRACK);
+            expect(Token.RBRACK, 'expected an array-style type declaration (' + value + '[])');
+            return {
+                type: Syntax.TypeApplication,
+                expression: {
+                    type: Syntax.NameExpression,
+                    name: 'Array'
+                },
+                applications: [expr]
+            };
+        }
+
+        return expr;
+    }
+
+    // TopLevelTypeExpression :=
+    //      TypeExpression
+    //    | TypeUnionList
+    //
+    // This rule is Google Closure Compiler extension, not ES4
+    // like,
+    //   { number | string }
+    // If strict to ES4, we should write it as
+    //   { (number|string) }
+    function parseTop() {
+        var expr, elements;
+
+        expr = parseTypeExpression();
+        if (token !== Token.PIPE) {
+            return expr;
+        }
+
+        elements = [expr];
+        consume(Token.PIPE);
+        while (true) {
+            elements.push(parseTypeExpression());
+            if (token !== Token.PIPE) {
+                break;
+            }
+            consume(Token.PIPE);
+        }
+
+        return {
+            type: Syntax.UnionType,
+            elements: elements
+        };
+    }
+
+    function parseTopParamType() {
+        var expr;
+
+        if (token === Token.REST) {
+            consume(Token.REST);
+            return {
+                type: Syntax.RestType,
+                expression: parseTop()
+            };
+        }
+
+        expr = parseTop();
+        if (token === Token.EQUAL) {
+            consume(Token.EQUAL);
+            return {
+                type: Syntax.OptionalType,
+                expression: expr
+            };
+        }
+
+        return expr;
+    }
+
+    function parseType(src, opt) {
+        var expr;
+
+        source = src;
+        length = source.length;
+        index = 0;
+        previous = 0;
+
+        next();
+        expr = parseTop();
+
+        if (opt && opt.midstream) {
+            return {
+                expression: expr,
+                index: previous
+            };
+        }
+
+        if (token !== Token.EOF) {
+            utility.throwError('not reach to EOF');
+        }
+
+        return expr;
+    }
+
+    function parseParamType(src, opt) {
+        var expr;
+
+        source = src;
+        length = source.length;
+        index = 0;
+        previous = 0;
+
+        next();
+        expr = parseTopParamType();
+
+        if (opt && opt.midstream) {
+            return {
+                expression: expr,
+                index: previous
+            };
+        }
+
+        if (token !== Token.EOF) {
+            utility.throwError('not reach to EOF');
+        }
+
+        return expr;
+    }
+
+    function stringifyImpl(node, compact, topLevel) {
+        var result, i, iz;
+
+        switch (node.type) {
+        case Syntax.NullableLiteral:
+            result = '?';
+            break;
+
+        case Syntax.AllLiteral:
+            result = '*';
+            break;
+
+        case Syntax.NullLiteral:
+            result = 'null';
+            break;
+
+        case Syntax.UndefinedLiteral:
+            result = 'undefined';
+            break;
+
+        case Syntax.VoidLiteral:
+            result = 'void';
+            break;
+
+        case Syntax.UnionType:
+            if (!topLevel) {
+                result = '(';
+            } else {
+                result = '';
+            }
+
+            for (i = 0, iz = node.elements.length; i < iz; ++i) {
+                result += stringifyImpl(node.elements[i], compact);
+                if ((i + 1) !== iz) {
+                    result += '|';
+                }
+            }
+
+            if (!topLevel) {
+                result += ')';
+            }
+            break;
+
+        case Syntax.ArrayType:
+            result = '[';
+            for (i = 0, iz = node.elements.length; i < iz; ++i) {
+                result += stringifyImpl(node.elements[i], compact);
+                if ((i + 1) !== iz) {
+                    result += compact ? ',' : ', ';
+                }
+            }
+            result += ']';
+            break;
+
+        case Syntax.RecordType:
+            result = '{';
+            for (i = 0, iz = node.fields.length; i < iz; ++i) {
+                result += stringifyImpl(node.fields[i], compact);
+                if ((i + 1) !== iz) {
+                    result += compact ? ',' : ', ';
+                }
+            }
+            result += '}';
+            break;
+
+        case Syntax.FieldType:
+            if (node.value) {
+                result = node.key + (compact ? ':' : ': ') + stringifyImpl(node.value, compact);
+            } else {
+                result = node.key;
+            }
+            break;
+
+        case Syntax.FunctionType:
+            result = compact ? 'function(' : 'function (';
+
+            if (node['this']) {
+                if (node['new']) {
+                    result += (compact ? 'new:' : 'new: ');
+                } else {
+                    result += (compact ? 'this:' : 'this: ');
+                }
+
+                result += stringifyImpl(node['this'], compact);
+
+                if (node.params.length !== 0) {
+                    result += compact ? ',' : ', ';
+                }
+            }
+
+            for (i = 0, iz = node.params.length; i < iz; ++i) {
+                result += stringifyImpl(node.params[i], compact);
+                if ((i + 1) !== iz) {
+                    result += compact ? ',' : ', ';
+                }
+            }
+
+            result += ')';
+
+            if (node.result) {
+                result += (compact ? ':' : ': ') + stringifyImpl(node.result, compact);
+            }
+            break;
+
+        case Syntax.ParameterType:
+            result = node.name + (compact ? ':' : ': ') + stringifyImpl(node.expression, compact);
+            break;
+
+        case Syntax.RestType:
+            result = '...';
+            if (node.expression) {
+                result += stringifyImpl(node.expression, compact);
+            }
+            break;
+
+        case Syntax.NonNullableType:
+            if (node.prefix) {
+                result = '!' + stringifyImpl(node.expression, compact);
+            } else {
+                result = stringifyImpl(node.expression, compact) + '!';
+            }
+            break;
+
+        case Syntax.OptionalType:
+            result = stringifyImpl(node.expression, compact) + '=';
+            break;
+
+        case Syntax.NullableType:
+            if (node.prefix) {
+                result = '?' + stringifyImpl(node.expression, compact);
+            } else {
+                result = stringifyImpl(node.expression, compact) + '?';
+            }
+            break;
+
+        case Syntax.NameExpression:
+            result = node.name;
+            break;
+
+        case Syntax.TypeApplication:
+            result = stringifyImpl(node.expression, compact) + '.<';
+            for (i = 0, iz = node.applications.length; i < iz; ++i) {
+                result += stringifyImpl(node.applications[i], compact);
+                if ((i + 1) !== iz) {
+                    result += compact ? ',' : ', ';
+                }
+            }
+            result += '>';
+            break;
+
+        case Syntax.StringLiteralType:
+            result = '"' + node.value + '"';
+            break;
+
+        case Syntax.NumericLiteralType:
+            result = String(node.value);
+            break;
+
+        case Syntax.BooleanLiteralType:
+            result = String(node.value);
+            break;
+
+        default:
+            utility.throwError('Unknown type ' + node.type);
+        }
+
+        return result;
+    }
+
+    function stringify(node, options) {
+        if (options == null) {
+            options = {};
+        }
+        return stringifyImpl(node, options.compact, options.topLevel);
+    }
+
+    exports.parseType = parseType;
+    exports.parseParamType = parseParamType;
+    exports.stringify = stringify;
+    exports.Syntax = Syntax;
+}());
+/* vim: set sw=4 ts=4 et tw=80 : */
diff --git a/lib/utility.js b/lib/utility.js
new file mode 100644
index 0000000..381580e
--- /dev/null
+++ b/lib/utility.js
@@ -0,0 +1,35 @@
+/*
+ * @fileoverview Utilities for Doctrine
+ * @author Yusuke Suzuki <utatane.tea at gmail.com>
+ */
+
+
+(function () {
+    'use strict';
+
+    var VERSION;
+
+    VERSION = require('../package.json').version;
+    exports.VERSION = VERSION;
+
+    function DoctrineError(message) {
+        this.name = 'DoctrineError';
+        this.message = message;
+    }
+    DoctrineError.prototype = (function () {
+        var Middle = function () { };
+        Middle.prototype = Error.prototype;
+        return new Middle();
+    }());
+    DoctrineError.prototype.constructor = DoctrineError;
+    exports.DoctrineError = DoctrineError;
+
+    function throwError(message) {
+        throw new DoctrineError(message);
+    }
+    exports.throwError = throwError;
+
+    exports.assert = require('assert');
+}());
+
+/* vim: set sw=4 ts=4 et tw=80 : */
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b11dc38
--- /dev/null
+++ b/package.json
@@ -0,0 +1,65 @@
+{
+  "name": "doctrine",
+  "description": "JSDoc parser",
+  "homepage": "https://github.com/eslint/doctrine",
+  "main": "lib/doctrine.js",
+  "version": "1.5.0",
+  "engines": {
+    "node": ">=0.10.0"
+  },
+  "directories": {
+    "lib": "./lib"
+  },
+  "files": [
+    "lib",
+    "LICENSE.BSD",
+    "LICENSE.closure-compiler",
+    "LICENSE.esprima",
+    "README.md"
+  ],
+  "maintainers": [
+    {
+      "name": "Nicholas C. Zakas",
+      "email": "nicholas+npm at nczconsulting.com",
+      "web": "https://www.nczonline.net"
+    },
+    {
+      "name": "Yusuke Suzuki",
+      "email": "utatane.tea at gmail.com",
+      "web": "https://github.com/Constellation"
+    }
+  ],
+  "repository": "eslint/doctrine",
+  "devDependencies": {
+    "coveralls": "^2.11.2",
+    "dateformat": "^1.0.11",
+    "eslint": "^1.10.3",
+    "eslint-release": "^0.10.0",
+    "istanbul": "^0.4.1",
+    "linefix": "^0.1.1",
+    "mocha": "^2.3.3",
+    "npm-license": "^0.3.1",
+    "semver": "^5.0.3",
+    "shelljs": "^0.5.3",
+    "shelljs-nodecli": "^0.1.1",
+    "should": "^5.0.1"
+  },
+  "licenses": [
+    {
+      "type": "BSD",
+      "url": "http://github.com/eslint/doctrine/raw/master/LICENSE.BSD"
+    }
+  ],
+  "scripts": {
+    "test": "npm run lint && node Makefile.js test",
+    "lint": "eslint lib/",
+    "release": "eslint-release",
+    "ci-release": "eslint-ci-release",
+    "alpharelease": "eslint-prerelease alpha",
+    "betarelease": "eslint-prerelease beta"
+  },
+  "dependencies": {
+    "esutils": "^2.0.2",
+    "isarray": "^1.0.0"
+  }
+}
diff --git a/test/midstream.js b/test/midstream.js
new file mode 100644
index 0000000..87dde20
--- /dev/null
+++ b/test/midstream.js
@@ -0,0 +1,43 @@
+/*
+ * @fileoverview Main Doctrine object
+ * @author Yusuke Suzuki <utatane.tea at gmail.com>
+ */
+
+/*global require describe it*/
+/*jslint node:true */
+'use strict';
+
+var fs = require('fs'),
+    path = require('path'),
+    root = path.join(path.dirname(fs.realpathSync(__filename)), '..'),
+    doctrine = require(root);
+require('should');
+
+describe('midstream', function () {
+    it('parseType', function () {
+        var res = doctrine.parseType('string name', { midstream: true });
+        res.should.eql({
+            "expression": {
+                "name": "string",
+                "type": "NameExpression"
+            },
+            "index": 6
+        });
+    });
+
+    it('parseParamType', function () {
+        var res = doctrine.parseParamType('...test ok', { midstream: true });
+        res.should.eql({
+            "expression": {
+                "expression": {
+                    "name": "test",
+                    "type": "NameExpression"
+                },
+                "type": "RestType"
+            },
+            "index": 7
+        });
+    });
+});
+
+/* vim: set sw=4 ts=4 et tw=80 : */
diff --git a/test/parse.js b/test/parse.js
new file mode 100644
index 0000000..abcde95
--- /dev/null
+++ b/test/parse.js
@@ -0,0 +1,2903 @@
+/*
+ * @fileoverview Main Doctrine object
+ * @author Yusuke Suzuki <utatane.tea at gmail.com>
+ */
+/*global require describe it*/
+/*jslint node:true */
+'use strict';
+
+var fs = require('fs'),
+    path = require('path'),
+    root = path.join(path.dirname(fs.realpathSync(__filename)), '..'),
+    doctrine = require(root);
+require('should');
+
+describe('parse', function () {
+    it('alias', function () {
+        var res = doctrine.parse('/** @alias */', { unwrap: true });
+        res.tags.should.have.length(0);
+    });
+
+    it('alias with name', function () {
+        var res = doctrine.parse('/** @alias aliasName */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'alias');
+        res.tags[0].should.have.property('name', 'aliasName');
+    });
+
+    it('alias with namepath', function () {
+        var res = doctrine.parse('/** @alias aliasName.OK */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'alias');
+        res.tags[0].should.have.property('name', 'aliasName.OK');
+    });
+
+    it('alias with namepath', function () {
+        var res = doctrine.parse('/** @alias module:mymodule/mymodule.init */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'alias');
+        res.tags[0].should.have.property('name', 'module:mymodule/mymodule.init');
+    });
+
+    it('alias with namepath with hyphen in it', function () {
+        var res = doctrine.parse('/** @alias module:mymodule/my-module */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'alias');
+        res.tags[0].should.have.property('name', 'module:mymodule/my-module');
+    });
+
+    it('const', function () {
+        var res = doctrine.parse('/** @const */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'const');
+    });
+
+    it('const with name', function () {
+        var res = doctrine.parse('/** @const constname */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'const');
+        res.tags[0].should.have.property('name', 'constname');
+    });
+
+    it('constant with name', function () {
+        var res = doctrine.parse('/** @constant constname */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'constant');
+        res.tags[0].should.have.property('name', 'constname');
+    });
+
+    it('const with type and name', function () {
+        var res = doctrine.parse('/** @const {String} constname */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'const');
+        res.tags[0].should.have.property('name', 'constname');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'String'
+        });
+    });
+
+    it('Const with type and name', function () {
+        var res = doctrine.parse('/** @Const {String} constname */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'Const');
+        res.tags[0].should.have.property('name', 'constname');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'String'
+        });
+    });
+
+    it('constant with type and name', function () {
+        var res = doctrine.parse('/** @constant {String} constname */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'constant');
+        res.tags[0].should.have.property('name', 'constname');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'String'
+        });
+    });
+
+    it('const multiple', function () {
+        var res = doctrine.parse("/**@const\n @const*/", { unwrap: true });
+        res.tags.should.have.length(2);
+        res.tags[0].should.have.property('title', 'const');
+        res.tags[1].should.have.property('title', 'const');
+    });
+
+    it('const double', function () {
+        var res = doctrine.parse("/**@const\n @const*/", { unwrap: true });
+        res.tags.should.have.length(2);
+        res.tags[0].should.have.property('title', 'const');
+        res.tags[1].should.have.property('title', 'const');
+    });
+
+    it('const triple', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @const @const",
+                " * @const @const",
+                " * @const @const",
+                " */"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(3);
+        res.tags[0].should.have.property('title', 'const');
+        res.tags[1].should.have.property('title', 'const');
+        res.tags[2].should.have.property('title', 'const');
+    });
+
+    it('constructor', function () {
+        var res = doctrine.parse('/** @constructor */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'constructor');
+    });
+
+    it('constructor with type', function () {
+        var res = doctrine.parse('/** @constructor {Object} */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'constructor');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'Object'
+        });
+    });
+
+    it('constructor with type and name', function () {
+        var res = doctrine.parse('/** @constructor {Object} objName */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'constructor');
+        res.tags[0].should.have.property('name', 'objName');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'Object'
+        });
+    });
+
+    it('class', function () {
+        var res = doctrine.parse('/** @class */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'class');
+    });
+
+    it('class with type', function () {
+        var res = doctrine.parse('/** @class {Object} */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'class');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'Object'
+        });
+    });
+
+    it('class with type and name', function () {
+        var res = doctrine.parse('/** @class {Object} objName */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'class');
+        res.tags[0].should.have.property('name', 'objName');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'Object'
+        });
+    });
+
+    it('deprecated', function () {
+        var res = doctrine.parse('/** @deprecated */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'deprecated');
+    });
+
+    it('deprecated', function () {
+        var res = doctrine.parse('/** @deprecated some text here describing why it is deprecated */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'deprecated');
+        res.tags[0].should.have.property('description', 'some text here describing why it is deprecated');
+    });
+
+    it('func', function () {
+        var res = doctrine.parse('/** @func */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'func');
+    });
+
+    it('func with name', function () {
+        var res = doctrine.parse('/** @func thingName.func */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'func');
+        res.tags[0].should.have.property('name', 'thingName.func');
+    });
+
+    it('func with type', function () {
+        var res = doctrine.parse('/** @func {Object} thingName.func */', { unwrap: true });
+        res.tags.should.have.length(0);
+        // func does not accept type
+    });
+
+    it('function', function () {
+        var res = doctrine.parse('/** @function */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'function');
+    });
+
+    it('function with name', function () {
+        var res = doctrine.parse('/** @function thingName.function */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'function');
+        res.tags[0].should.have.property('name', 'thingName.function');
+    });
+
+    it('function with type', function () {
+        var res = doctrine.parse('/** @function {Object} thingName.function */', { unwrap: true });
+        res.tags.should.have.length(0);
+        // function does not accept type
+    });
+
+    it('member', function () {
+        var res = doctrine.parse('/** @member */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'member');
+    });
+
+    it('member with name', function () {
+        var res = doctrine.parse('/** @member thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'member');
+        res.tags[0].should.have.property('name', 'thingName.name');
+    });
+
+    it('member with type', function () {
+        var res = doctrine.parse('/** @member {Object} thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'member');
+        res.tags[0].should.have.property('name', 'thingName.name');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'Object'
+        });
+    });
+
+    it('method', function () {
+        var res = doctrine.parse('/** @method */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'method');
+    });
+
+    it('method with name', function () {
+        var res = doctrine.parse('/** @method thingName.function */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'method');
+        res.tags[0].should.have.property('name', 'thingName.function');
+    });
+
+    it('method with type', function () {
+        var res = doctrine.parse('/** @method {Object} thingName.function */', { unwrap: true });
+        res.tags.should.have.length(0);
+        // method does not accept type
+    });
+
+    it('mixes', function () {
+        var res = doctrine.parse('/** @mixes */', { unwrap: true });
+        res.tags.should.have.length(0);
+    });
+
+    it('mixes with name', function () {
+        var res = doctrine.parse('/** @mixes thingName */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'mixes');
+        res.tags[0].should.have.property('name', 'thingName');
+    });
+
+    it('mixes with namepath', function () {
+        var res = doctrine.parse('/** @mixes thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'mixes');
+        res.tags[0].should.have.property('name', 'thingName.name');
+    });
+
+    it('mixin', function () {
+        var res = doctrine.parse('/** @mixin */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'mixin');
+    });
+
+    it('mixin with name', function () {
+        var res = doctrine.parse('/** @mixin thingName */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'mixin');
+        res.tags[0].should.have.property('name', 'thingName');
+    });
+
+    it('mixin with namepath', function () {
+        var res = doctrine.parse('/** @mixin thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'mixin');
+        res.tags[0].should.have.property('name', 'thingName.name');
+    });
+
+    it('module', function () {
+        var res = doctrine.parse('/** @module */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'module');
+    });
+
+    it('module with name', function () {
+        var res = doctrine.parse('/** @module thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'module');
+        res.tags[0].should.have.property('name', 'thingName.name');
+    });
+
+    it('module with name that has a hyphen in it', function () {
+        var res = doctrine.parse('/** @module thingName-name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'module');
+        res.tags[0].should.have.property('name', 'thingName-name');
+    });
+
+    it('module with type', function () {
+        var res = doctrine.parse('/** @module {Object} thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'module');
+        res.tags[0].should.have.property('name', 'thingName.name');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'Object'
+        });
+    });
+
+    it('module with path', function () {
+        var res = doctrine.parse('/** @module path/to/thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'module');
+        res.tags[0].should.have.property('name', 'path/to/thingName.name');
+    });
+
+    it('name', function () {
+        var res = doctrine.parse('/** @name thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'name');
+        res.tags[0].should.have.property('name', 'thingName.name');
+    });
+
+    it('name', function () {
+        var res = doctrine.parse('/** @name thingName#name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'name');
+        res.tags[0].should.have.property('name', 'thingName#name');
+    });
+
+    it('name', function () {
+        var res = doctrine.parse('/** @name thingName~name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'name');
+        res.tags[0].should.have.property('name', 'thingName~name');
+    });
+
+    it('name', function () {
+        var res = doctrine.parse('/** @name {thing} thingName.name */', { unwrap: true });
+        // name does not accept type
+        res.tags.should.have.length(0);
+    });
+
+    it('namespace', function () {
+        var res = doctrine.parse('/** @namespace */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'namespace');
+    });
+
+    it('namespace with name', function () {
+        var res = doctrine.parse('/** @namespace thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'namespace');
+        res.tags[0].should.have.property('name', 'thingName.name');
+    });
+
+    it('namespace with type', function () {
+        var res = doctrine.parse('/** @namespace {Object} thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'namespace');
+        res.tags[0].should.have.property('name', 'thingName.name');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'Object'
+        });
+    });
+
+    it('param', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {String} userName",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'userName');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'String'
+        });
+    });
+
+    it('param with properties', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {String} user.name",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'user.name');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'String'
+        });
+    });
+
+    it('param with properties with description', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {String} user.name - hi",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'user.name');
+        res.tags[0].should.have.property('description', 'hi');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'String'
+        });
+    });
+
+    it('param with array properties with description', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {string} employee[].name - hi",
+                " */"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'employee[].name');
+        res.tags[0].should.have.property('description', 'hi');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'string'
+        });
+    });
+
+    it('param with array properties without description', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {string} employee[].name",
+                " */"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'employee[].name');
+        res.tags[0].should.have.property('description', null);
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'string'
+        });
+    });
+
+    it('arg with properties', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @arg {String} user.name",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'arg');
+        res.tags[0].should.have.property('name', 'user.name');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'String'
+        });
+    });
+
+    it('argument with properties', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @argument {String} user.name",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'argument');
+        res.tags[0].should.have.property('name', 'user.name');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'String'
+        });
+    });
+
+    it('param typeless', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param something [bye] hi",
+                "*/"
+            ].join('\n'), { unwrap: true, sloppy: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.eql({
+            title: 'param',
+            type: undefined,
+            name: 'something',
+            description: "[bye] hi"
+        });
+
+        var res = doctrine.parse(
+        [
+            "/**",
+            " * @param userName",
+            "*/"
+        ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.eql({
+            title: 'param',
+            type: null,
+            name: 'userName',
+            description: null
+        });
+
+        var res = doctrine.parse(
+        [
+            "/**",
+            " * @param userName Something descriptive",
+            "*/"
+        ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.eql({
+            title: 'param',
+            type: null,
+            name: 'userName',
+            description: 'Something descriptive'
+        });
+
+        var res = doctrine.parse(
+        [
+            "/**",
+            " * @param user.name Something descriptive",
+            "*/"
+        ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.eql({
+            title: 'param',
+            type: null,
+            name: 'user.name',
+            description: 'Something descriptive'
+        });
+    });
+
+    it('param broken', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {String} userName",
+                " * @param {String userName",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'userName');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'String'
+        });
+    });
+
+    it('param record', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {{ok:String}} userName",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'userName');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'RecordType',
+            fields: [{
+                type: 'FieldType',
+                key: 'ok',
+                value: {
+                    type: 'NameExpression',
+                    name: 'String'
+                }
+            }]
+        });
+    });
+
+    it('param record broken', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {{ok:String} userName",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.be.empty;
+    });
+
+    it('param multiple lines', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {string|",
+                " *     number} userName",
+                " * }}",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'userName');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'UnionType',
+            elements: [{
+                type: 'NameExpression',
+                name: 'string'
+            }, {
+                type: 'NameExpression',
+                name: 'number'
+            }]
+        });
+    });
+
+    it('param without braces', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param string name description",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'string');
+        res.tags[0].should.have.property('type', null);
+        res.tags[0].should.have.property('description', 'name description');
+    });
+
+    it('param w/ hyphen before description', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {string} name - description",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.eql({
+            title: 'param',
+            type: {
+                type: 'NameExpression',
+                name: 'string'
+            },
+            name: 'name',
+            description: 'description'
+        });
+    });
+
+    it('param w/ hyphen + leading space before description', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {string} name -   description",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.eql({
+            title: 'param',
+            type: {
+                type: 'NameExpression',
+                name: 'string'
+            },
+            name: 'name',
+            description: '  description'
+        });
+    });
+
+    it('description and param separated by blank line', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * Description",
+                " * blah blah blah",
+                " *",
+                " * @param {string} name description",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.description.should.eql('Description\nblah blah blah');
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'name');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'string'
+        });
+        res.tags[0].should.have.property('description', 'description');
+    });
+
+    it('regular block comment instead of jsdoc-style block comment', function () {
+        var res = doctrine.parse(
+            [
+                "/*",
+                " * Description",
+                " * blah blah blah",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.description.should.eql("Description\nblah blah blah");
+    });
+
+    it('augments', function () {
+        var res = doctrine.parse('/** @augments */', { unwrap: true });
+        res.tags.should.have.length(1);
+    });
+
+    it('augments with name', function () {
+        var res = doctrine.parse('/** @augments ClassName */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'augments');
+        res.tags[0].should.have.property('name', 'ClassName');
+    });
+
+    it('augments with type', function () {
+        var res = doctrine.parse('/** @augments {ClassName} */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'augments');
+        res.tags[0].should.have.property('type', {
+          type: 'NameExpression',
+          name: 'ClassName'
+        });
+    });
+
+    it('augments with name', function () {
+        var res = doctrine.parse('/** @augments ClassName.OK */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'augments');
+        res.tags[0].should.have.property('name', 'ClassName.OK');
+    });
+
+    it('extends', function () {
+        var res = doctrine.parse('/** @extends */', { unwrap: true });
+        res.tags.should.have.length(1);
+    });
+
+    it('extends with name', function () {
+        var res = doctrine.parse('/** @extends ClassName */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'extends');
+        res.tags[0].should.have.property('name', 'ClassName');
+    });
+
+    it('extends with type', function () {
+        var res = doctrine.parse('/** @extends {ClassName} */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'extends');
+        res.tags[0].should.have.property('type', {
+          type: 'NameExpression',
+          name: 'ClassName'
+        });
+    });
+
+    it('extends with namepath', function () {
+        var res = doctrine.parse('/** @extends ClassName.OK */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'extends');
+        res.tags[0].should.have.property('name', 'ClassName.OK');
+    });
+
+    it('extends with namepath', function () {
+        var res = doctrine.parse('/** @extends module:path/ClassName~OK */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'extends');
+        res.tags[0].should.have.property('name', 'module:path/ClassName~OK');
+    });
+
+    it('prop', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @prop {string} thingName - does some stuff",
+              "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'prop');
+        res.tags[0].should.have.property('description', 'does some stuff');
+        res.tags[0].type.should.have.property('name', 'string');
+        res.tags[0].should.have.property('name', 'thingName');
+    });
+
+    it('prop without type', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @prop thingName - does some stuff",
+              "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(0);
+    });
+
+
+    it('property', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @property {string} thingName - does some stuff",
+              "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'property');
+        res.tags[0].should.have.property('description', 'does some stuff');
+        res.tags[0].type.should.have.property('name', 'string');
+        res.tags[0].should.have.property('name', 'thingName');
+    });
+
+    it('property without type', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @property thingName - does some stuff",
+              "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(0);
+    });
+
+
+    it('property with optional type', function() {
+        var res = doctrine.parse(
+            ["/**",
+                "* testtypedef",
+                "* @typedef {object} abc",
+                "* @property {String} [val] value description",
+                "*/"].join('\n'), {
+                unwrap: true,
+                sloppy: true
+            });
+
+        res.tags[1].should.have.property('title', 'property');
+        res.tags[1].should.have.property('type');
+        res.tags[1]['type'].should.have.property('type', "OptionalType");
+    });
+
+
+    it('property with nested name', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @property {string} thingName.name - does some stuff",
+              "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'property');
+        res.tags[0].should.have.property('description', 'does some stuff');
+        res.tags[0].type.should.have.property('name', 'string');
+        res.tags[0].should.have.property('name', 'thingName.name');
+    });
+
+    it('throws', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @throws {Error} if something goes wrong",
+              " */"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'throws');
+        res.tags[0].should.have.property('description', 'if something goes wrong');
+        res.tags[0].type.should.have.property('name', 'Error');
+    });
+
+    it('throws without type', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @throws if something goes wrong",
+              " */"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'throws');
+        res.tags[0].should.have.property('description', 'if something goes wrong');
+    });
+
+    it('kind', function () {
+        var res = doctrine.parse('/** @kind class */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'kind');
+        res.tags[0].should.have.property('kind', 'class');
+    });
+
+    it('kind error', function () {
+        var res = doctrine.parse('/** @kind ng */', { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('errors');
+        res.tags[0].errors.should.have.length(1);
+        res.tags[0].errors[0].should.equal('Invalid kind name \'ng\'');
+    });
+
+    it('todo', function () {
+        var res = doctrine.parse('/** @todo Write the documentation */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'todo');
+        res.tags[0].should.have.property('description', 'Write the documentation');
+    });
+
+    it('typedef', function () {
+        var res = doctrine.parse('/** @typedef {Object} NumberLike */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'Object'
+        });
+        res.tags[0].should.have.property('name', 'NumberLike');
+    });
+
+    it('summary', function () {
+        // japanese lang
+        var res = doctrine.parse('/** @summary ゆるゆり3期おめでとー */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'summary');
+        res.tags[0].should.have.property('description', 'ゆるゆり3期おめでとー');
+    });
+
+    it('variation', function () {
+        // japanese lang
+        var res = doctrine.parse('/** @variation 42 */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'variation');
+        res.tags[0].should.have.property('variation', 42);
+    });
+
+    it('variation error', function () {
+        // japanese lang
+        var res = doctrine.parse('/** @variation Animation */', { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('errors');
+        res.tags[0].errors.should.have.length(1);
+        res.tags[0].errors[0].should.equal('Invalid variation \'Animation\'');
+    });
+
+    it('access', function () {
+        var res = doctrine.parse('/** @access public */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'access');
+        res.tags[0].should.have.property('access', 'public');
+    });
+
+    it('access error', function () {
+        var res = doctrine.parse('/** @access ng */', { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('errors');
+        res.tags[0].errors.should.have.length(1);
+        res.tags[0].errors[0].should.equal('Invalid access name \'ng\'');
+    });
+
+    it('public', function () {
+        var res = doctrine.parse('/** @public */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'public');
+    });
+
+    it('public type and description', function () {
+        var res = doctrine.parse('/** @public {number} ok */', { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'public');
+        res.tags[0].should.have.property('description', 'ok');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'number'
+        });
+    });
+
+    it('protected', function () {
+        var res = doctrine.parse('/** @protected */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'protected');
+    });
+
+    it('protected type and description', function () {
+        var res = doctrine.parse('/** @protected {number} ok */', { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'protected');
+        res.tags[0].should.have.property('description', 'ok');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'number'
+        });
+    });
+
+    it('private', function () {
+        var res = doctrine.parse('/** @private */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'private');
+    });
+
+    it('private type and description', function () {
+        var res = doctrine.parse('/** @private {number} ok */', { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'private');
+        res.tags[0].should.have.property('description', 'ok');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'number'
+        });
+    });
+
+    it('readonly', function () {
+        var res = doctrine.parse('/** @readonly */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'readonly');
+    });
+
+    it('readonly error', function () {
+        var res = doctrine.parse('/** @readonly ng */', { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('errors');
+        res.tags[0].errors.should.have.length(1);
+        res.tags[0].errors[0].should.equal('Unknown content \'ng\'');
+    });
+
+    it('requires', function () {
+        var res = doctrine.parse('/** @requires */', { unwrap: true });
+        res.tags.should.have.length(0);
+    });
+
+    it('requires with module name', function () {
+        var res = doctrine.parse('/** @requires name.path */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'requires');
+        res.tags[0].should.have.property('name', 'name.path');
+    });
+
+    it('global', function () {
+        var res = doctrine.parse('/** @global */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'global');
+    });
+
+    it('global error', function () {
+        var res = doctrine.parse('/** @global ng */', { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('errors');
+        res.tags[0].errors.should.have.length(1);
+        res.tags[0].errors[0].should.equal('Unknown content \'ng\'');
+    });
+
+    it('inner', function () {
+        var res = doctrine.parse('/** @inner */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'inner');
+    });
+
+    it('inner error', function () {
+        var res = doctrine.parse('/** @inner ng */', { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('errors');
+        res.tags[0].errors.should.have.length(1);
+        res.tags[0].errors[0].should.equal('Unknown content \'ng\'');
+    });
+
+    it('instance', function () {
+        var res = doctrine.parse('/** @instance */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'instance');
+    });
+
+    it('instance error', function () {
+        var res = doctrine.parse('/** @instance ng */', { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('errors');
+        res.tags[0].errors.should.have.length(1);
+        res.tags[0].errors[0].should.equal('Unknown content \'ng\'');
+    });
+
+    it('since', function () {
+        var res = doctrine.parse('/** @since 1.2.1 */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'since');
+        res.tags[0].should.have.property('description', '1.2.1');
+    });
+
+    it('static', function () {
+        var res = doctrine.parse('/** @static */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'static');
+    });
+
+    it('static error', function () {
+        var res = doctrine.parse('/** @static ng */', { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('errors');
+        res.tags[0].errors.should.have.length(1);
+        res.tags[0].errors[0].should.equal('Unknown content \'ng\'');
+    });
+
+    it('this', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @this thingName",
+              "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'this');
+        res.tags[0].should.have.property('name', 'thingName');
+    });
+
+    it('this with namepath', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @this thingName.name",
+              "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'this');
+        res.tags[0].should.have.property('name', 'thingName.name');
+    });
+
+    it('this with name expression', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @this {thingName.name}",
+              "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'this');
+        res.tags[0].should.have.property('name', 'thingName.name');
+    });
+
+    it('this error with type application', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @this {Array<string>}",
+              "*/"
+            ].join('\n'), { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'this');
+        res.tags[0].should.have.property('errors');
+        res.tags[0].errors.should.have.length(1);
+        res.tags[0].errors[0].should.equal('Invalid name for this');
+    });
+
+    it('this error', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @this",
+              "*/"
+            ].join('\n'), { unwrap: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'this');
+        res.tags[0].should.have.property('errors');
+        res.tags[0].errors.should.have.length(1);
+        res.tags[0].errors[0].should.equal('Missing or invalid tag name');
+    });
+
+    it('var', function () {
+        var res = doctrine.parse('/** @var */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'var');
+    });
+
+    it('var with name', function () {
+        var res = doctrine.parse('/** @var thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'var');
+        res.tags[0].should.have.property('name', 'thingName.name');
+    });
+
+    it('var with type', function () {
+        var res = doctrine.parse('/** @var {Object} thingName.name */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'var');
+        res.tags[0].should.have.property('name', 'thingName.name');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'Object'
+        });
+    });
+
+    it('version', function () {
+        var res = doctrine.parse('/** @version 1.2.1 */', { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'version');
+        res.tags[0].should.have.property('description', '1.2.1');
+    });
+
+    it('incorrect name', function () {
+        var res = doctrine.parse('/** @name thingName#%name */', { unwrap: true });
+        // name does not accept type
+        res.tags.should.have.length(0);
+        res.should.eql({
+            "description": "",
+            "tags": [
+            ]
+        });
+    });
+
+    it('string literal property', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @typedef {Object} comment",
+                " * @property {('public'|'protected'|'private')} access",
+                "*/"
+            ].join('\n'), { unwrap: true });
+
+        res.tags.should.have.length(2);
+        res.tags[1].should.have.property('title', 'property');
+        res.tags[1].should.have.property('name', 'access');
+        res.tags[1].type.should.have.property('type', 'UnionType');
+        res.tags[1].type.elements.should.have.length(3);
+        res.tags[1].type.elements.should.containEql({type: 'StringLiteralType', value: 'public'});
+        res.tags[1].type.elements.should.containEql({type: 'StringLiteralType', value: 'private'});
+        res.tags[1].type.elements.should.containEql({type: 'StringLiteralType', value: 'protected'});
+    });
+
+    it('numeric literal property', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @typedef {Object} comment",
+                " * @property {(-42|1.5|0)} access",
+                "*/"
+            ].join('\n'), { unwrap: true });
+
+        res.tags.should.have.length(2);
+        res.tags[1].should.have.property('title', 'property');
+        res.tags[1].should.have.property('name', 'access');
+        res.tags[1].type.should.have.property('type', 'UnionType');
+        res.tags[1].type.elements.should.have.length(3);
+        res.tags[1].type.elements.should.containEql({type: 'NumericLiteralType', value: -42});
+        res.tags[1].type.elements.should.containEql({type: 'NumericLiteralType', value: 1.5});
+        res.tags[1].type.elements.should.containEql({type: 'NumericLiteralType', value: 0});
+    });
+
+    it('boolean literal property', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @typedef {Object} comment",
+                " * @property {(true|false)} access",
+                "*/"
+            ].join('\n'), { unwrap: true });
+
+        res.tags.should.have.length(2);
+        res.tags[1].should.have.property('title', 'property');
+        res.tags[1].should.have.property('name', 'access');
+        res.tags[1].type.should.have.property('type', 'UnionType');
+        res.tags[1].type.elements.should.have.length(2);
+        res.tags[1].type.elements.should.containEql({type: 'BooleanLiteralType', value: true});
+        res.tags[1].type.elements.should.containEql({type: 'BooleanLiteralType', value: false});
+    });
+
+    it('complex union with literal types', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @typedef {({ok: true, data: string} | {ok: false, error: Error})} Result",
+                "*/"
+            ].join('\n'), { unwrap: true });
+
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'typedef');
+        res.tags[0].should.have.property('name', 'Result');
+        res.tags[0].type.should.have.property('type', 'UnionType');
+        res.tags[0].type.elements.should.have.length(2);
+
+        var e0 = res.tags[0].type.elements[0];
+        e0.should.have.property('type', 'RecordType');
+        e0.fields.should.have.length(2);
+        e0.fields.should.containEql({type: 'FieldType', key: 'ok',
+          value: {type: 'BooleanLiteralType', value: true}});
+        e0.fields.should.containEql({type: 'FieldType', key: 'data',
+          value: {type: 'NameExpression', name: 'string'}});
+
+        var e1 = res.tags[0].type.elements[1];
+        e1.should.have.property('type', 'RecordType');
+        e1.fields.should.have.length(2);
+        e1.fields.should.containEql({type: 'FieldType', key: 'ok',
+          value: {type: 'BooleanLiteralType', value: false}});
+        e1.fields.should.containEql({type: 'FieldType', key: 'error',
+          value: {type: 'NameExpression', name: 'Error'}});
+    });
+});
+
+describe('parseType', function () {
+    it('union type closure-compiler extended', function () {
+        var type = doctrine.parseType("string|number");
+        type.should.eql({
+            type: 'UnionType',
+            elements: [{
+                type: 'NameExpression',
+                name: 'string'
+            }, {
+                type: 'NameExpression',
+                name: 'number'
+            }]
+        });
+    });
+
+    it('empty union type', function () {
+        var type = doctrine.parseType("()");
+        type.should.eql({
+            type: 'UnionType',
+            elements: []
+        });
+    });
+
+    it('comma last array type', function () {
+        var type = doctrine.parseType("[string,]");
+        type.should.eql({
+            type: 'ArrayType',
+            elements: [{
+                type: 'NameExpression',
+                name: 'string'
+            }]
+        });
+    });
+
+    it('array type of all literal', function () {
+        var type = doctrine.parseType("[*]");
+        type.should.eql({
+            type: 'ArrayType',
+            elements: [{
+                type: 'AllLiteral'
+            }]
+        });
+    });
+
+    it('array type of nullable literal', function () {
+        var type = doctrine.parseType("[?]");
+        type.should.eql({
+            type: 'ArrayType',
+            elements: [{
+                type: 'NullableLiteral'
+            }]
+        });
+    });
+
+    it('comma last record type', function () {
+        var type = doctrine.parseType("{,}");
+        type.should.eql({
+            type: 'RecordType',
+            fields: []
+        });
+    });
+
+    it('type application', function () {
+        var type = doctrine.parseType("Array.<String>");
+        type.should.eql({
+            type: 'TypeApplication',
+            expression: {
+                type: 'NameExpression',
+                name: 'Array'
+            },
+            applications: [{
+                type: 'NameExpression',
+                name: 'String'
+            }]
+        });
+    });
+
+    it('type application with NullableLiteral', function () {
+        var type = doctrine.parseType("Array<?>");
+        type.should.eql({
+            type: 'TypeApplication',
+            expression: {
+                type: 'NameExpression',
+                name: 'Array'
+            },
+            applications: [{
+                type: 'NullableLiteral'
+            }]
+        });
+    });
+
+    it('type application with multiple patterns', function () {
+        var type = doctrine.parseType("Array.<String, Number>");
+        type.should.eql({
+            type: 'TypeApplication',
+            expression: {
+                type: 'NameExpression',
+                name: 'Array'
+            },
+            applications: [{
+                type: 'NameExpression',
+                name: 'String'
+            }, {
+                type: 'NameExpression',
+                name: 'Number'
+            }]
+        });
+    });
+
+    it('type application without dot', function () {
+        var type = doctrine.parseType("Array<String>");
+        type.should.eql({
+            type: 'TypeApplication',
+            expression: {
+                type: 'NameExpression',
+                name: 'Array'
+            },
+            applications: [{
+                type: 'NameExpression',
+                name: 'String'
+            }]
+        });
+    });
+
+    it('array-style type application', function () {
+        var type = doctrine.parseType("String[]");
+        type.should.eql({
+            type: 'TypeApplication',
+            expression: {
+                type: 'NameExpression',
+                name: 'Array'
+            },
+            applications: [{
+                type: 'NameExpression',
+                name: 'String'
+            }]
+        });
+    });
+
+    it('function type simple', function () {
+        var type = doctrine.parseType("function()");
+        type.should.eql({
+		 "type": "FunctionType",
+		 "params": [],
+		 "result": null
+		});
+    });
+
+    it('function type with name', function () {
+        var type = doctrine.parseType("function(a)");
+        type.should.eql({
+		 "type": "FunctionType",
+		 "params": [
+		{
+		   "type": "NameExpression",
+		   "name": "a"
+		  }
+			 ],
+		 "result": null
+		});
+    });
+    it('function type with name and type', function () {
+        var type = doctrine.parseType("function(a:b)");
+        type.should.eql({
+		 "type": "FunctionType",
+		 "params": [
+		  {
+		   "type": "ParameterType",
+		   "name": "a",
+		   "expression": {
+		    "type": "NameExpression",
+		    "name": "b"
+		   }
+		  }
+		 ],
+		 "result": null
+		});
+    });
+    it('function type with optional param', function () {
+        var type = doctrine.parseType("function(a=)");
+        type.should.eql({
+		 "type": "FunctionType",
+		 "params": [
+		  {
+		   "type": "OptionalType",
+		   "expression": {
+		    "type": "NameExpression",
+		    "name": "a"
+		   }
+		  }
+		 ],
+		 "result": null
+		});
+    });
+    it('function type with optional param name and type', function () {
+        var type = doctrine.parseType("function(a:b=)");
+        type.should.eql({
+		 "type": "FunctionType",
+		 "params": [
+		  {
+		   "type": "OptionalType",
+		   "expression": {
+		    "type": "ParameterType",
+		    "name": "a",
+		    "expression": {
+		     "type": "NameExpression",
+		     "name": "b"
+		    }
+		   }
+		  }
+		 ],
+		 "result": null
+		});
+    });
+    it('function type with rest param', function () {
+        var type = doctrine.parseType("function(...a)");
+        type.should.eql({
+		 "type": "FunctionType",
+		 "params": [
+		  {
+		   "type": "RestType",
+		   "expression": {
+		    "type": "NameExpression",
+		    "name": "a"
+		   }
+		  }
+		 ],
+		 "result": null
+		});
+    });
+    it('function type with rest param name and type', function () {
+        var type = doctrine.parseType("function(...a:b)");
+        type.should.eql({
+		 "type": "FunctionType",
+		 "params": [
+		  {
+		   "type": "RestType",
+		   "expression": {
+		    "type": "ParameterType",
+		    "name": "a",
+		    "expression": {
+		     "type": "NameExpression",
+		     "name": "b"
+		    }
+		   }
+		  }
+		 ],
+		 "result": null
+		});
+    });
+
+    it('function type with optional rest param', function () {
+        var type = doctrine.parseType("function(...a=)");
+        type.should.eql({
+		 "type": "FunctionType",
+		 "params": [
+		  {
+			"type": "RestType",
+			"expression": {
+			   "type": "OptionalType",
+			   "expression": {
+			    "type": "NameExpression",
+			    "name": "a"
+			   }
+			  }
+			}
+		 ],
+		 "result": null
+		});
+    });
+    it('function type with optional rest param name and type', function () {
+        var type = doctrine.parseType("function(...a:b=)");
+        type.should.eql({
+		 "type": "FunctionType",
+		 "params": [
+		  {
+			"type": "RestType",
+			"expression": {
+			   "type": "OptionalType",
+			   "expression": {
+			    "type": "ParameterType",
+			    "name": "a",
+			    "expression": {
+			     "type": "NameExpression",
+			     "name": "b"
+			    }
+			  }
+			}
+		 }],
+		 "result": null
+		});
+    });
+
+    it('string value in type', function () {
+        var type;
+
+        type = doctrine.parseType("{'ok':String}");
+        type.should.eql({
+            "fields": [
+                {
+                    "key": "ok",
+                    "type": "FieldType",
+                    "value": {
+                        "name": "String",
+                        "type": "NameExpression"
+                    }
+                }
+            ],
+            "type": "RecordType"
+        });
+
+        type = doctrine.parseType('{"\\r\\n\\t\\u2028\\x20\\u20\\b\\f\\v\\\r\n\\\n\\0\\07\\012\\o":String}');
+        type.should.eql({
+            "fields": [
+                {
+                    "key": "\r\n\t\u2028\x20u20\b\f\v\0\u0007\u000ao",
+                    "type": "FieldType",
+                    "value": {
+                        "name": "String",
+                        "type": "NameExpression"
+                    }
+                }
+            ],
+            "type": "RecordType"
+        });
+
+        doctrine.parseType.bind(doctrine, "{'ok\":String}").should.throw('unexpected quote');
+        doctrine.parseType.bind(doctrine, "{'o\n':String}").should.throw('unexpected quote');
+    });
+
+    it('number value in type', function () {
+        var type;
+
+        type = doctrine.parseType("{20:String}");
+        type.should.eql({
+            "fields": [
+                {
+                    "key": "20",
+                    "type": "FieldType",
+                    "value": {
+                        "name": "String",
+                        "type": "NameExpression"
+                    }
+                }
+            ],
+            "type": "RecordType"
+        });
+
+        type = doctrine.parseType("{.2:String, 30:Number, 0x20:String}");
+        type.should.eql({
+            "fields": [
+                {
+                    "key": "0.2",
+                    "type": "FieldType",
+                    "value": {
+                        "name": "String",
+                        "type": "NameExpression"
+                    }
+                },
+                {
+                    "key": "30",
+                    "type": "FieldType",
+                    "value": {
+                        "name": "Number",
+                        "type": "NameExpression"
+                    }
+                },
+                {
+                    "key": "32",
+                    "type": "FieldType",
+                    "value": {
+                        "name": "String",
+                        "type": "NameExpression"
+                    }
+                }
+            ],
+            "type": "RecordType"
+        });
+
+
+        type = doctrine.parseType("{0X2:String, 0:Number, 100e200:String, 10e-20:Number}");
+        type.should.eql({
+            "fields": [
+                {
+                    "key": "2",
+                    "type": "FieldType",
+                    "value": {
+                        "name": "String",
+                        "type": "NameExpression"
+                    }
+                },
+                {
+                    "key": "0",
+                    "type": "FieldType",
+                    "value": {
+                        "name": "Number",
+                        "type": "NameExpression"
+                    }
+                },
+                {
+                    "key": "1e+202",
+                    "type": "FieldType",
+                    "value": {
+                        "name": "String",
+                        "type": "NameExpression"
+                    }
+                },
+                {
+                    "key": "1e-19",
+                    "type": "FieldType",
+                    "value": {
+                        "name": "Number",
+                        "type": "NameExpression"
+                    }
+                }
+            ],
+            "type": "RecordType"
+        });
+
+
+        doctrine.parseType.bind(doctrine, "{0x:String}").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{0x").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{0xd").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{0x2_:").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{021:").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{021_:").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{021").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{08").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{0y").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{0").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{100e2").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{100e-2").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{100e-200:").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "{100e:").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "function(number=, string)").should.throw('not reach to EOF');
+    });
+
+    it('dotted type', function () {
+        var type;
+        type = doctrine.parseType("Cocoa.Cappuccino");
+        type.should.eql({
+            "name": "Cocoa.Cappuccino",
+            "type": "NameExpression"
+        });
+    });
+
+    it('rest array type', function () {
+        var type;
+        type = doctrine.parseType("[string,...string]");
+        type.should.eql({
+            "elements": [
+                {
+                    "name": "string",
+                    "type": "NameExpression"
+                },
+                {
+                    "expression": {
+                        "name": "string",
+                        "type": "NameExpression"
+                    },
+                "type": "RestType"
+                }
+            ],
+            "type": "ArrayType"
+        });
+    });
+
+    it ('nullable type', function () {
+        var type;
+        type = doctrine.parseType("string?");
+        type.should.eql({
+            "expression": {
+                "name": "string",
+                "type": "NameExpression"
+            },
+            "prefix": false,
+            "type": "NullableType"
+        });
+    });
+
+    it ('non-nullable type', function () {
+        var type;
+        type = doctrine.parseType("string!");
+        type.should.eql({
+            "expression": {
+                "name": "string",
+                "type": "NameExpression"
+            },
+            "prefix": false,
+            "type": "NonNullableType"
+        });
+    });
+
+    it ('toplevel multiple pipe type', function () {
+        var type;
+        type = doctrine.parseType("string|number|Test");
+        type.should.eql({
+            "elements": [
+                {
+                    "name": "string",
+                    "type": "NameExpression"
+                },
+                {
+                    "name": "number",
+                    "type": "NameExpression"
+                },
+                {
+                    "name": "Test",
+                    "type": "NameExpression"
+                }
+            ],
+            "type": "UnionType"
+        });
+    });
+
+    it('string literal type', function () {
+        var type;
+        type = doctrine.parseType('"Hello, World"');
+        type.should.eql({
+            type: 'StringLiteralType',
+            value: 'Hello, World'
+        });
+    });
+
+    it('numeric literal type', function () {
+        var type;
+        type = doctrine.parseType('32');
+        type.should.eql({
+            type: 'NumericLiteralType',
+            value: 32
+        });
+        type = doctrine.parseType('-142.42');
+        type.should.eql({
+            type: 'NumericLiteralType',
+            value: -142.42
+        });
+    });
+
+    it('boolean literal type', function () {
+        var type;
+        type = doctrine.parseType('true');
+        type.should.eql({
+            type: 'BooleanLiteralType',
+            value: true
+        });
+        type = doctrine.parseType('false');
+        type.should.eql({
+            type: 'BooleanLiteralType',
+            value: false
+        });
+    });
+
+    it('illegal tokens', function () {
+        doctrine.parseType.bind(doctrine, ".").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, ".d").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "(").should.throw('unexpected token');
+        doctrine.parseType.bind(doctrine, "Test.").should.throw('unexpected token');
+    });
+});
+
+describe('parseParamType', function () {
+    it('question', function () {
+        var type = doctrine.parseParamType("?");
+        type.should.eql({
+            type: 'NullableLiteral'
+        });
+    });
+
+    it('question option', function () {
+        var type = doctrine.parseParamType("?=");
+        type.should.eql({
+            type: 'OptionalType',
+            expression: {
+                type: 'NullableLiteral'
+            }
+        });
+    });
+
+    it('function option parameters former', function () {
+        var type = doctrine.parseParamType("function(?, number)");
+        type.should.eql({
+            type: 'FunctionType',
+            params: [{
+                type: 'NullableLiteral'
+            }, {
+                type: 'NameExpression',
+                name: 'number'
+            }],
+            result: null
+        });
+    });
+
+    it('function option parameters latter', function () {
+        var type = doctrine.parseParamType("function(number, ?)");
+        type.should.eql({
+            type: 'FunctionType',
+            params: [{
+                type: 'NameExpression',
+                name: 'number'
+            }, {
+                type: 'NullableLiteral'
+            }],
+            result: null
+        });
+    });
+
+    it('function type union', function () {
+        var type = doctrine.parseParamType("function(): ?|number");
+        type.should.eql({
+            type: 'UnionType',
+            elements: [{
+                type: 'FunctionType',
+                params: [],
+                result: {
+                    type: 'NullableLiteral'
+                }
+            }, {
+                type: 'NameExpression',
+                name: 'number'
+            }]
+        });
+    });
+});
+
+describe('invalid', function () {
+    it('empty union pipe', function () {
+        doctrine.parseType.bind(doctrine, "(|)").should.throw();
+        doctrine.parseType.bind(doctrine, "(string|)").should.throw();
+        doctrine.parseType.bind(doctrine, "(string||)").should.throw();
+    });
+
+    it('comma only array type', function () {
+        doctrine.parseType.bind(doctrine, "[,]").should.throw();
+    });
+
+    it('comma only record type', function () {
+        doctrine.parseType.bind(doctrine, "{,,}").should.throw();
+    });
+
+    it('incorrect bracket', function () {
+        doctrine.parseParamType.bind(doctrine, "int[").should.throw();
+    });
+});
+
+describe('tags option', function() {
+	it ('only param', function() {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @const @const",
+                " * @param {String} y",
+                " */"
+            ].join('\n'), { tags: ['param'], unwrap:true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'y');
+     });
+
+	it ('param and type', function() {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @const x",
+                " * @param {String} y",
+                " * @type {String} ",
+                " */"
+            ].join('\n'), { tags: ['param', 'type'], unwrap:true });
+        res.tags.should.have.length(2);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'y');
+        res.tags[1].should.have.property('title', 'type');
+        res.tags[1].should.have.property('type');
+        res.tags[1].type.should.have.property('name', 'String');
+     });
+
+});
+
+describe('invalid tags', function() {
+	it ('bad tag 1', function() {
+        doctrine.parse.bind(doctrine,
+            [
+                "/**",
+                " * @param {String} hucairz",
+                " */"
+            ].join('\n'), { tags: 1, unwrap:true }).should.throw();
+     });
+
+	it ('bad tag 2', function() {
+        doctrine.parse.bind(doctrine,
+            [
+                "/**",
+                " * @param {String} hucairz",
+                " */"
+            ].join('\n'), { tags: ['a', 1], unwrap:true }).should.throw();
+     });
+});
+
+describe('optional params', function() {
+
+    // should fail since sloppy option not set
+    it('failure 0', function() {
+        doctrine.parse(
+        ["/**", " * @param {String} [val]", " */"].join('\n'), {
+            unwrap: true
+        }).should.eql({
+            "description": "",
+            "tags": []
+        });
+    });
+
+    it('failure 1', function() {
+        doctrine.parse(
+        ["/**", " * @param [val", " */"].join('\n'), {
+            unwrap: true, sloppy: true
+        }).should.eql({
+            "description": "",
+            "tags": []
+        });
+    });
+
+    it('success 1', function() {
+        doctrine.parse(
+        ["/**", " * @param {String} [val]", " */"].join('\n'), {
+            unwrap: true, sloppy: true
+        }).should.eql({
+            "description": "",
+            "tags": [{
+                "title": "param",
+                "description": null,
+                "type": {
+                    "type": "OptionalType",
+                    "expression": {
+                        "type": "NameExpression",
+                        "name": "String"
+                    }
+                },
+                "name": "val"
+            }]
+        });
+    });
+    it('success 2', function() {
+        doctrine.parse(
+        ["/**", " * @param {String=} val", " */"].join('\n'), {
+            unwrap: true, sloppy: true
+        }).should.eql({
+            "description": "",
+            "tags": [{
+                "title": "param",
+                "description": null,
+                "type": {
+                    "type": "OptionalType",
+                    "expression": {
+                        "type": "NameExpression",
+                        "name": "String"
+                    }
+                },
+                "name": "val"
+            }]
+        });
+    });
+
+    it('success 3', function() {
+        doctrine.parse(
+            ["/**", " * @param {String=} [val=abc] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+            "description": "",
+            "tags": [{
+                "title": "param",
+                "description": "some description",
+                "type": {
+                    "type": "OptionalType",
+                    "expression": {
+                        "type": "NameExpression",
+                        "name": "String"
+                    }
+                },
+                "name": "val",
+                "default": "abc"
+            }]
+        });
+    });
+
+    it('success 4', function() {
+        doctrine.parse(
+            ["/**", " * @param {String=} [val = abc] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+            "description": "",
+            "tags": [{
+                "title": "param",
+                "description": "some description",
+                "type": {
+                    "type": "OptionalType",
+                    "expression": {
+                        "type": "NameExpression",
+                        "name": "String"
+                    }
+                },
+                "name": "val",
+                "default": "abc"
+            }]
+        });
+    });
+
+    it('default string', function() {
+        doctrine.parse(
+            ["/**", " * @param {String} [val=\"foo\"] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+            "description": "",
+            "tags": [{
+                "title": "param",
+                "description": "some description",
+                "type": {
+                    "type": "OptionalType",
+                    "expression": {
+                        "type": "NameExpression",
+                        "name": "String"
+                    }
+                },
+                "name": "val",
+                "default": "\"foo\""
+            }]
+        });
+    });
+
+    it('default string surrounded by whitespace', function() {
+        doctrine.parse(
+            ["/**", " * @param {String} [val=   'foo'  ] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+            "description": "",
+            "tags": [{
+                "title": "param",
+                "description": "some description",
+                "type": {
+                    "type": "OptionalType",
+                    "expression": {
+                        "type": "NameExpression",
+                        "name": "String"
+                    }
+                },
+                "name": "val",
+                "default": "'foo'"
+            }]
+        });
+    });
+
+    it('should preserve whitespace in default string', function() {
+        doctrine.parse(
+            ["/**", " * @param {String} [val=   \"   foo\"  ] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+            "description": "",
+            "tags": [{
+                "title": "param",
+                "description": "some description",
+                "type": {
+                    "type": "OptionalType",
+                    "expression": {
+                        "type": "NameExpression",
+                        "name": "String"
+                    }
+                },
+                "name": "val",
+                "default": "\"   foo\""
+            }]
+        });
+    });
+
+    it('default array', function() {
+        doctrine.parse(
+            ["/**", " * @param {String} [val=['foo']] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+            "description": "",
+            "tags": [{
+                "title": "param",
+                "description": "some description",
+                "type": {
+                    "type": "OptionalType",
+                    "expression": {
+                        "type": "NameExpression",
+                        "name": "String"
+                    }
+                },
+                "name": "val",
+                "default": "['foo']"
+            }]
+        });
+    });
+
+    it('default array', function() {
+        doctrine.parse(
+            ["/**", " * @param {String} [val=['foo']] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+            "description": "",
+            "tags": [{
+                "title": "param",
+                "description": "some description",
+                "type": {
+                    "type": "OptionalType",
+                    "expression": {
+                        "type": "NameExpression",
+                        "name": "String"
+                    }
+                },
+                "name": "val",
+                "default": "['foo']"
+            }]
+        });
+    });
+
+    it('default array within white spaces', function() {
+        doctrine.parse(
+            ["/**", " * @param {String} [val = [ 'foo' ]] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+            "description": "",
+            "tags": [{
+                "title": "param",
+                "description": "some description",
+                "type": {
+                    "type": "OptionalType",
+                    "expression": {
+                        "type": "NameExpression",
+                        "name": "String"
+                    }
+                },
+                "name": "val",
+                "default": "['foo']"
+            }]
+        });
+    });
+
+    it('line numbers', function() {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @constructor",
+                " * @param {string} foo",
+                " * @returns {string}",
+                " *",
+                " * @example",
+                " * f('blah'); // => undefined",
+                " */"
+            ].join('\n'),
+            { unwrap: true, lineNumbers: true }
+        );
+
+        res.tags[0].should.have.property('lineNumber', 1);
+        res.tags[1].should.have.property('lineNumber', 2);
+        res.tags[2].should.have.property('lineNumber', 3);
+        res.tags[3].should.have.property('lineNumber', 5);
+    });
+
+    it('example caption', function() {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @example <caption>hi</caption>",
+                " * f('blah'); // => undefined",
+                " */"
+            ].join('\n'),
+            { unwrap: true, lineNumbers: true }
+        );
+
+        res.tags[0].description.should.eql("f('blah'); // => undefined");
+        res.tags[0].caption.should.eql("hi");
+    });
+
+    it('should handle \\r\\n line endings correctly', function() {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {string} foo",
+                " * @returns {string}",
+                " *",
+                " * @example",
+                " * f('blah'); // => undefined",
+                " */"
+            ].join('\r\n'),
+            { unwrap: true, lineNumbers: true }
+        );
+
+        res.tags[0].should.have.property('lineNumber', 1);
+        res.tags[1].should.have.property('lineNumber', 2);
+        res.tags[2].should.have.property('lineNumber', 4);
+    });
+});
+
+describe('optional properties', function() {
+
+    // should fail since sloppy option not set
+    it('failure 0', function() {
+        doctrine.parse(
+            [   "/**",
+                " * @property {String} [val] some description",
+                " */"].join('\n'), {
+                unwrap: true
+            }).should.eql({
+                "description": "",
+                "tags": []
+            });
+    });
+
+    it('failure 1', function() {
+        doctrine.parse(
+            ["/**", " * @property [val", " */"].join('\n'), {
+                unwrap: true, sloppy: true
+            }).should.eql({
+                "description": "",
+                "tags": []
+            });
+    });
+
+    it('success 1', function() {
+        doctrine.parse(
+            ["/**", " * @property {String} [val]", " */"].join('\n'), {
+                unwrap: true, sloppy: true
+            }).should.eql({
+                "description": "",
+                "tags": [{
+                    "title": "property",
+                    "description": null,
+                    "type": {
+                        "type": "OptionalType",
+                        "expression": {
+                            "type": "NameExpression",
+                            "name": "String"
+                        }
+                    },
+                    "name": "val"
+                }]
+            });
+    });
+
+    it('success 2', function() {
+        doctrine.parse(
+            ["/**", " * @property {String=} val", " */"].join('\n'), {
+                unwrap: true, sloppy: true
+            }).should.eql({
+                "description": "",
+                "tags": [{
+                    "title": "property",
+                    "description": null,
+                    "type": {
+                        "type": "OptionalType",
+                        "expression": {
+                            "type": "NameExpression",
+                            "name": "String"
+                        }
+                    },
+                    "name": "val"
+                }]
+            });
+    });
+
+    it('success 3', function() {
+        doctrine.parse(
+            ["/**", " * @property {String=} [val=abc] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+                "description": "",
+                "tags": [{
+                    "title": "property",
+                    "description": "some description",
+                    "type": {
+                        "type": "OptionalType",
+                        "expression": {
+                            "type": "NameExpression",
+                            "name": "String"
+                        }
+                    },
+                    "name": "val",
+                    "default": "abc"
+                }]
+            });
+    });
+
+    it('success 4', function() {
+        doctrine.parse(
+            ["/**", " * @property {String=} [val = abc] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+                "description": "",
+                "tags": [{
+                    "title": "property",
+                    "description": "some description",
+                    "type": {
+                        "type": "OptionalType",
+                        "expression": {
+                            "type": "NameExpression",
+                            "name": "String"
+                        }
+                    },
+                    "name": "val",
+                    "default": "abc"
+                }]
+            });
+    });
+
+    it('default string', function() {
+        doctrine.parse(
+            ["/**", " * @property {String} [val=\"foo\"] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+                "description": "",
+                "tags": [{
+                    "title": "property",
+                    "description": "some description",
+                    "type": {
+                        "type": "OptionalType",
+                        "expression": {
+                            "type": "NameExpression",
+                            "name": "String"
+                        }
+                    },
+                    "name": "val",
+                    "default": "\"foo\""
+                }]
+            });
+    });
+
+    it('default string surrounded by whitespace', function() {
+        doctrine.parse(
+            ["/**", " * @property {String} [val=   'foo'  ] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+                "description": "",
+                "tags": [{
+                    "title": "property",
+                    "description": "some description",
+                    "type": {
+                        "type": "OptionalType",
+                        "expression": {
+                            "type": "NameExpression",
+                            "name": "String"
+                        }
+                    },
+                    "name": "val",
+                    "default": "'foo'"
+                }]
+            });
+    });
+
+    it('should preserve whitespace in default string', function() {
+        doctrine.parse(
+            ["/**", " * @property {String} [val=   \"   foo\"  ] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+                "description": "",
+                "tags": [{
+                    "title": "property",
+                    "description": "some description",
+                    "type": {
+                        "type": "OptionalType",
+                        "expression": {
+                            "type": "NameExpression",
+                            "name": "String"
+                        }
+                    },
+                    "name": "val",
+                    "default": "\"   foo\""
+                }]
+            });
+    });
+
+    it('default array', function() {
+        doctrine.parse(
+            ["/**", " * @property {String} [val=['foo']] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+                "description": "",
+                "tags": [{
+                    "title": "property",
+                    "description": "some description",
+                    "type": {
+                        "type": "OptionalType",
+                        "expression": {
+                            "type": "NameExpression",
+                            "name": "String"
+                        }
+                    },
+                    "name": "val",
+                    "default": "['foo']"
+                }]
+            });
+    });
+
+    it('default array within white spaces', function() {
+        doctrine.parse(
+            ["/**", " * @property {String} [val = [ 'foo' ]] some description", " */"].join('\n'),
+            { unwrap: true, sloppy: true}
+        ).should.eql({
+                "description": "",
+                "tags": [{
+                    "title": "property",
+                    "description": "some description",
+                    "type": {
+                        "type": "OptionalType",
+                        "expression": {
+                            "type": "NameExpression",
+                            "name": "String"
+                        }
+                    },
+                    "name": "val",
+                    "default": "['foo']"
+                }]
+            });
+    });
+});
+
+describe('recovery tests', function() {
+	it ('params 2', function () {
+		var res = doctrine.parse(
+            [
+                "@param f",
+                "@param {string} f2"
+            ].join('\n'), { recoverable: true });
+
+         // ensure both parameters are OK
+         res.tags.should.have.length(2);
+         res.tags[0].should.have.property('title', 'param');
+         res.tags[0].should.have.property('type', null);
+         res.tags[0].should.have.property('name', 'f');
+
+         res.tags[1].should.have.property('title', 'param');
+         res.tags[1].should.have.property('type');
+         res.tags[1].type.should.have.property('name', 'string');
+         res.tags[1].type.should.have.property('type', 'NameExpression');
+         res.tags[1].should.have.property('name', 'f2');
+	});
+
+	it ('params 2', function () {
+		var res = doctrine.parse(
+            [
+                "@param string f",
+                "@param {string} f2"
+            ].join('\n'), { recoverable: true });
+
+         // ensure first parameter is OK even with invalid type name
+         res.tags.should.have.length(2);
+         res.tags[0].should.have.property('title', 'param');
+         res.tags[0].should.have.property('type', null);
+         res.tags[0].should.have.property('name', 'string');
+         res.tags[0].should.have.property('description', 'f');
+
+         res.tags[1].should.have.property('title', 'param');
+         res.tags[1].should.have.property('type');
+         res.tags[1].type.should.have.property('name', 'string');
+         res.tags[1].type.should.have.property('type', 'NameExpression');
+         res.tags[1].should.have.property('name', 'f2');
+	});
+
+	it ('return 1', function() {
+		var res = doctrine.parse(
+            [
+                "@returns"
+            ].join('\n'), { recoverable: true });
+
+         // return tag should exist
+         res.tags.should.have.length(1);
+         res.tags[0].should.have.property('title', 'returns');
+         res.tags[0].should.have.property('type', null);
+	});
+	it ('return 2', function() {
+		var res = doctrine.parse(
+            [
+                "@returns",
+				"@param {string} f2"
+            ].join('\n'), { recoverable: true });
+
+         // return tag should exist as well as next tag
+         res.tags.should.have.length(2);
+         res.tags[0].should.have.property('title', 'returns');
+         res.tags[0].should.have.property('type', null);
+
+         res.tags[1].should.have.property('title', 'param');
+         res.tags[1].should.have.property('type');
+         res.tags[1].type.should.have.property('name', 'string');
+         res.tags[1].type.should.have.property('type', 'NameExpression');
+         res.tags[1].should.have.property('name', 'f2');
+	});
+
+	it ('return no type', function() {
+		var res = doctrine.parse(
+            [
+                "@return a value"
+            ].join('\n'));
+
+         // return tag should exist
+         res.tags.should.have.length(1);
+         res.tags[0].should.have.property('title', 'return');
+         res.tags[0].should.have.property('type', null);
+         res.tags[0].should.have.property('description', 'a value');
+	});
+
+	it ('extra @ 1', function() {
+		var res = doctrine.parse(
+            [
+                "@",
+                "@returns",
+				"@param {string} f2"
+            ].join('\n'), { recoverable: true });
+
+         // empty tag name shouldn't affect subsequent tags
+         res.tags.should.have.length(3);
+         res.tags[0].should.have.property('title', '');
+         res.tags[0].should.not.have.property('type');
+
+         res.tags[1].should.have.property('title', 'returns');
+         res.tags[1].should.have.property('type', null);
+
+         res.tags[2].should.have.property('title', 'param');
+         res.tags[2].should.have.property('type');
+         res.tags[2].type.should.have.property('name', 'string');
+         res.tags[2].type.should.have.property('type', 'NameExpression');
+         res.tags[2].should.have.property('name', 'f2');
+	});
+
+	it ('extra @ 2', function() {
+		var res = doctrine.parse(
+            [
+                "@ invalid name",
+				"@param {string} f2"
+            ].join('\n'), { recoverable: true });
+
+         // empty tag name shouldn't affect subsequent tags
+         res.tags.should.have.length(2);
+         res.tags[0].should.have.property('title', '');
+         res.tags[0].should.not.have.property('type');
+         res.tags[0].should.not.have.property('name');
+         res.tags[0].should.have.property('description', 'invalid name');
+
+         res.tags[1].should.have.property('title', 'param');
+         res.tags[1].should.have.property('type');
+         res.tags[1].type.should.have.property('name', 'string');
+         res.tags[1].type.should.have.property('type', 'NameExpression');
+         res.tags[1].should.have.property('name', 'f2');
+	});
+
+	it ('invalid tag 1', function() {
+		var res = doctrine.parse(
+            [
+                "@111 invalid name",
+				"@param {string} f2"
+            ].join('\n'), { recoverable: true });
+
+         // invalid tag name shouldn't affect subsequent tags
+         res.tags.should.have.length(2);
+         res.tags[0].should.have.property('title', '111');
+         res.tags[0].should.not.have.property('type');
+         res.tags[0].should.not.have.property('name');
+         res.tags[0].should.have.property('description', 'invalid name');
+
+         res.tags[1].should.have.property('title', 'param');
+         res.tags[1].should.have.property('type');
+         res.tags[1].type.should.have.property('name', 'string');
+         res.tags[1].type.should.have.property('type', 'NameExpression');
+         res.tags[1].should.have.property('name', 'f2');
+	});
+
+	it ('invalid tag 1', function() {
+		var res = doctrine.parse(
+            [
+                "@111",
+				"@param {string} f2"
+            ].join('\n'), { recoverable: true });
+
+         // invalid tag name shouldn't affect subsequent tags
+         res.tags.should.have.length(2);
+         res.tags[0].should.have.property('title', '111');
+         res.tags[0].should.not.have.property('type');
+         res.tags[0].should.not.have.property('name');
+         res.tags[0].should.have.property('description', null);
+
+         res.tags[1].should.have.property('title', 'param');
+         res.tags[1].should.have.property('type');
+         res.tags[1].type.should.have.property('name', 'string');
+         res.tags[1].type.should.have.property('type', 'NameExpression');
+         res.tags[1].should.have.property('name', 'f2');
+	});
+
+	it ('should not crash on bad type in @param without name', function() {
+		var res = doctrine.parse("@param {Function(DOMNode)}", { recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.eql({
+            "description": null,
+            "errors": [
+                "not reach to EOF",
+                "Missing or invalid tag name"
+            ],
+            "name": null,
+            "title": "param",
+            "type": null
+        });
+    });
+
+	it ('should not crash on bad type in @param in sloppy mode', function() {
+		var res = doctrine.parse("@param {int[} [x]", { sloppy: true, recoverable: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.eql({
+            "description": null,
+            "errors": [
+                "expected an array-style type declaration (int[])"
+            ],
+            "name": "x",
+            "title": "param",
+            "type": null
+        });
+    });
+});
+
+describe('exported Syntax', function() {
+	it ('members', function () {
+        doctrine.Syntax.should.eql({
+            NullableLiteral: 'NullableLiteral',
+            AllLiteral: 'AllLiteral',
+            NullLiteral: 'NullLiteral',
+            UndefinedLiteral: 'UndefinedLiteral',
+            VoidLiteral: 'VoidLiteral',
+            UnionType: 'UnionType',
+            ArrayType: 'ArrayType',
+            BooleanLiteralType: 'BooleanLiteralType',
+            RecordType: 'RecordType',
+            FieldType: 'FieldType',
+            FunctionType: 'FunctionType',
+            ParameterType: 'ParameterType',
+            RestType: 'RestType',
+            NonNullableType: 'NonNullableType',
+            OptionalType: 'OptionalType',
+            NullableType: 'NullableType',
+            NameExpression: 'NameExpression',
+            TypeApplication: 'TypeApplication',
+            StringLiteralType: 'StringLiteralType',
+            NumericLiteralType: 'NumericLiteralType'
+        });
+    });
+});
+
+describe('@ mark contained descriptions', function () {
+    it ('comment description #10', function () {
+        doctrine.parse(
+            [
+                '/**',
+                ' * Prevents the default action. It is equivalent to',
+                ' * {@code e.preventDefault()}, but can be used as the callback argument of',
+                ' * {@link goog.events.listen} without declaring another function.',
+                ' * @param {!goog.events.Event} e An event.',
+                ' */'
+            ].join('\n'),
+            { unwrap: true, sloppy: true }).should.eql({
+            'description': 'Prevents the default action. It is equivalent to\n{@code e.preventDefault()}, but can be used as the callback argument of\n{@link goog.events.listen} without declaring another function.',
+            'tags': [{
+                'title': 'param',
+                'description': 'An event.',
+                'type': {
+                    'type': 'NonNullableType',
+                    'expression': {
+                        'type': 'NameExpression',
+                        'name': 'goog.events.Event'
+                    },
+                    'prefix': true
+                },
+                'name': 'e'
+            }]
+        });
+    });
+
+    it ('tag description', function () {
+        doctrine.parse(
+            [
+                '/**',
+                ' * Prevents the default action. It is equivalent to',
+                ' * @param {!goog.events.Event} e An event.',
+                ' * {@code e.preventDefault()}, but can be used as the callback argument of',
+                ' * {@link goog.events.listen} without declaring another function.',
+                ' */'
+            ].join('\n'),
+            { unwrap: true, sloppy: true }).should.eql({
+            'description': 'Prevents the default action. It is equivalent to',
+            'tags': [{
+                'title': 'param',
+                'description': 'An event.\n{@code e.preventDefault()}, but can be used as the callback argument of\n{@link goog.events.listen} without declaring another function.',
+                'type': {
+                    'type': 'NonNullableType',
+                    'expression': {
+                        'type': 'NameExpression',
+                        'name': 'goog.events.Event'
+                    },
+                    'prefix': true
+                },
+                'name': 'e'
+            }]
+        });
+    });
+});
+
+describe('function', function () {
+    it ('recognize "function" type', function () {
+		var res = doctrine.parse(
+            [
+                "@param {function} foo description",
+            ].join('\n'), {});
+         res.tags.should.have.length(1);
+         res.tags[0].should.have.property('title', 'param');
+         res.tags[0].should.have.property('type');
+         res.tags[0].type.should.eql({
+             "name": "function",
+             "type": "NameExpression"
+         });
+         res.tags[0].should.have.property('name', 'foo');
+         res.tags[0].should.have.property('description', 'description');
+    });
+});
+
+describe('tagged namepaths', function () {
+    it ('recognize module:', function () {
+        var res = doctrine.parse(
+            [
+                "@alias module:Foo.bar"
+            ].join('\n'), {});
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'alias');
+        res.tags[0].should.have.property('name', 'module:Foo.bar');
+        res.tags[0].should.have.property('description', null);
+    });
+
+    it ('recognize external:', function () {
+        var res = doctrine.parse(
+            [
+                "@param {external:Foo.bar} baz description"
+            ].join('\n'), {});
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+         res.tags[0].type.should.eql({
+             "name": "external:Foo.bar",
+             "type": "NameExpression"
+         });
+        res.tags[0].should.have.property('name', 'baz');
+        res.tags[0].should.have.property('description', 'description');
+    });
+
+    it ('recognize event:', function () {
+        var res = doctrine.parse(
+            [
+                "@function event:Foo.bar"
+            ].join('\n'), {});
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'function');
+        res.tags[0].should.have.property('name', 'event:Foo.bar');
+        res.tags[0].should.have.property('description', null);
+    });
+
+    it ('invalid bogus:', function () {
+        var res = doctrine.parse(
+            [
+                "@method bogus:Foo.bar"
+            ].join('\n'), {});
+        res.tags.should.have.length(0);
+    });
+});
+
+/* vim: set sw=4 ts=4 et tw=80 : */
diff --git a/test/strict.js b/test/strict.js
new file mode 100644
index 0000000..e2cc3fb
--- /dev/null
+++ b/test/strict.js
@@ -0,0 +1,149 @@
+/*
+ * @fileoverview Main Doctrine object
+ * @author Yusuke Suzuki <utatane.tea at gmail.com>
+ */
+/*global require describe it*/
+/*jslint node:true */
+'use strict';
+
+var fs = require('fs'),
+    path = require('path'),
+    root = path.join(path.dirname(fs.realpathSync(__filename)), '..'),
+    doctrine = require(root);
+require('should');
+
+describe('strict parse', function () {
+    // https://github.com/Constellation/doctrine/issues/21
+    it('unbalanced braces', function () {
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * @param {const",
+                    " */"
+                ].join('\n'), { unwrap: true, strict: true });
+        }).should.throw('Braces are not balanced');
+
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * @param {const",
+                    " */"
+                ].join('\n'), { unwrap: true });
+        }).should.not.throw();
+
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * Description",
+                    " * @param {string name Param description",
+                    " * @param {int} foo Bar",
+                    " */"
+                ].join('\n'), { unwrap: true, strict: true });
+        }).should.throw('Braces are not balanced');
+
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * Description",
+                    " * @param {string name Param description",
+                    " * @param {int} foo Bar",
+                    " */"
+                ].join('\n'), { unwrap: true });
+        }).should.not.throw();
+
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * Description",
+                    " * @returns {int",
+                    " */"
+                ].join('\n'), { unwrap: true, strict: true });
+        }).should.throw('Braces are not balanced');
+
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * Description",
+                    " * @returns {int",
+                    " */"
+                ].join('\n'), { unwrap: true });
+        }).should.not.throw();
+    });
+
+    // https://github.com/Constellation/doctrine/issues/21
+    it('incorrect tag starting with @@', function () {
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * @@version",
+                    " */"
+                ].join('\n'), { unwrap: true, strict: true });
+        }).should.throw('Missing or invalid title');
+
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * @@version",
+                    " */"
+                ].join('\n'), { unwrap: true });
+        }).should.not.throw();
+
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * Description",
+                    " * @@param {string} name Param description",
+                    " */"
+                ].join('\n'), { unwrap: true, strict: true });
+        }).should.throw('Missing or invalid title');
+
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * Description",
+                    " * @@param {string} name Param description",
+                    " */"
+                ].join('\n'), { unwrap: true });
+        }).should.not.throw();
+
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * Description",
+                    " * @kind ng",
+                    " */"
+                ].join('\n'), { unwrap: true, strict: true });
+        }).should.throw('Invalid kind name \'ng\'');
+
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * Description",
+                    " * @variation Animation",
+                    " */"
+                ].join('\n'), { unwrap: true, strict: true });
+        }).should.throw('Invalid variation \'Animation\'');
+
+        (function () {
+            doctrine.parse(
+                [
+                    "/**",
+                    " * Description",
+                    " * @access ng",
+                    " */"
+                ].join('\n'), { unwrap: true, strict: true });
+        }).should.throw('Invalid access name \'ng\'');
+    });
+});
diff --git a/test/stringify.js b/test/stringify.js
new file mode 100644
index 0000000..f59bb2b
--- /dev/null
+++ b/test/stringify.js
@@ -0,0 +1,423 @@
+/*
+ * @fileoverview Main Doctrine object
+ * @author Yusuke Suzuki <utatane.tea at gmail.com>
+ */
+/*global require describe it*/
+/*jslint node:true */
+'use strict';
+
+var fs = require('fs'),
+    path = require('path'),
+    root = path.join(path.dirname(fs.realpathSync(__filename)), '..'),
+    doctrine = require(root),
+    assert = require('assert');
+require('should');
+
+// tests for the stringify function.
+// ensure that we can parse and then stringify and the results are identical
+describe('stringify', function () {
+
+    function testStringify(text) {
+        it (text, function() {
+            var result = doctrine.parse("@param {" + text + "} name");
+            //    console.log("Parse Tree: " + JSON.stringify(result, null, " "));
+            var stringed = doctrine.type.stringify(result.tags[0].type, {compact:true});
+            stringed.should.equal(text);
+        });
+    }
+
+    // simple
+    testStringify("String");
+    testStringify("*");
+    testStringify("null");
+    testStringify("undefined");
+    testStringify("void");
+    //testStringify("?=");  // Failing
+
+    // rest
+    testStringify("...string");
+    testStringify("...[string]");
+    testStringify("...[[string]]");
+
+    // optional, nullable, nonnullable
+    testStringify("string=");
+    testStringify("?string");
+    testStringify("!string");
+    testStringify("!string=");
+
+    // type applications
+    testStringify("Array.<String>");
+    testStringify("Array.<String,Number>");
+
+    // union types
+    testStringify("()");
+    testStringify("(String|Number)");
+
+    // Arrays
+    testStringify("[String]");
+    testStringify("[String,Number]");
+    testStringify("[(String|Number)]");
+
+    // Record types
+    testStringify("{a}");
+    testStringify("{a:String}");
+    testStringify("{a:String,b}");
+    testStringify("{a:String,b:object}");
+    testStringify("{a:String,b:foo.bar.baz}");
+    testStringify("{a:(String|Number),b,c:Array.<String>}");
+    testStringify("...{a:(String|Number),b,c:Array.<String>}");
+    testStringify("{a:(String|Number),b,c:Array.<String>}=");
+
+    // fn types
+    testStringify("function(a)");
+    testStringify("function(a):String");
+    testStringify("function(a:number):String");
+    testStringify("function(a:number,b:Array.<(String|Number|Object)>):String");
+    testStringify("function(a:number,callback:function(a:Array.<(String|Number|Object)>):boolean):String");
+    testStringify("function(a:(string|number),this:string,new:true):function():number");
+    testStringify("function(a:(string|number),this:string,new:true):function(a:function(val):result):number");
+
+    // literal types
+    testStringify('"Hello, World!"');
+    testStringify("-142.35");
+});
+
+describe('literals', function() {
+    it('NullableLiteral', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.NullableLiteral
+        }).should.equal('?');
+    });
+
+    it('AllLiteral', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.AllLiteral
+        }).should.equal('*');
+    });
+
+    it('NullLiteral', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.NullLiteral
+        }).should.equal('null');
+    });
+
+    it('UndefinedLiteral', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.UndefinedLiteral
+        }).should.equal('undefined');
+    });
+
+    it('StringLiteralType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.StringLiteralType,
+            value: 'Hello, World!'
+        }).should.equal('"Hello, World!"');
+    });
+
+    it('NumericLiteralType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.NumericLiteralType,
+            value: -142.35
+        }).should.equal('-142.35');
+    });
+
+    it('BooleanLiteralType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.BooleanLiteralType,
+            value: true
+        }).should.equal('true');
+        doctrine.type.stringify({
+            type: doctrine.Syntax.BooleanLiteralType,
+            value: false
+        }).should.equal('false');
+    });
+});
+
+describe('Expression', function () {
+    it('NameExpression', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.NameExpression,
+            name: 'this.is.valid'
+        }).should.equal('this.is.valid');
+
+        doctrine.type.stringify({
+            type: doctrine.Syntax.NameExpression,
+            name: 'String'
+        }).should.equal('String');
+    });
+
+    it('ArrayType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.ArrayType,
+            elements: [{
+                type: doctrine.Syntax.NameExpression,
+                name: 'String'
+            }]
+        }).should.equal('[String]');
+
+        doctrine.type.stringify({
+            type: doctrine.Syntax.ArrayType,
+            elements: [{
+                type: doctrine.Syntax.NameExpression,
+                name: 'String'
+            }, {
+                type: doctrine.Syntax.NameExpression,
+                name: 'Number'
+            }]
+        }).should.equal('[String, Number]');
+
+        doctrine.type.stringify({
+            type: doctrine.Syntax.ArrayType,
+            elements: []
+        }).should.equal('[]');
+    });
+
+    it('RecordType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.RecordType,
+            fields: [{
+                type: doctrine.Syntax.FieldType,
+                key: 'name',
+                value: null
+            }]
+        }).should.equal('{name}');
+
+        doctrine.type.stringify({
+            type: doctrine.Syntax.RecordType,
+            fields: [{
+                type: doctrine.Syntax.FieldType,
+                key: 'name',
+                value: {
+                    type: doctrine.Syntax.NameExpression,
+                    name: 'String'
+                }
+            }]
+        }).should.equal('{name: String}');
+
+        doctrine.type.stringify({
+            type: doctrine.Syntax.RecordType,
+            fields: [{
+                type: doctrine.Syntax.FieldType,
+                key: 'string',
+                value: {
+                    type: doctrine.Syntax.NameExpression,
+                    name: 'String'
+                }
+            }, {
+                type: doctrine.Syntax.FieldType,
+                key: 'number',
+                value: {
+                    type: doctrine.Syntax.NameExpression,
+                    name: 'Number'
+                }
+            }]
+        }).should.equal('{string: String, number: Number}');
+
+        doctrine.type.stringify({
+            type: doctrine.Syntax.RecordType,
+            fields: []
+        }).should.equal('{}');
+    });
+
+    it('UnionType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.UnionType,
+            elements: [{
+                type: doctrine.Syntax.NameExpression,
+                name: 'String'
+            }]
+        }).should.equal('(String)');
+
+        doctrine.type.stringify({
+            type: doctrine.Syntax.UnionType,
+            elements: [{
+                type: doctrine.Syntax.NameExpression,
+                name: 'String'
+            }, {
+                type: doctrine.Syntax.NameExpression,
+                name: 'Number'
+            }]
+        }).should.equal('(String|Number)');
+
+        doctrine.type.stringify({
+            type: doctrine.Syntax.UnionType,
+            elements: [{
+                type: doctrine.Syntax.NameExpression,
+                name: 'String'
+            }, {
+                type: doctrine.Syntax.NameExpression,
+                name: 'Number'
+            }]
+        }, { topLevel: true }).should.equal('String|Number');
+    });
+
+    it('RestType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.RestType,
+            expression: {
+                type: doctrine.Syntax.NameExpression,
+                name: 'String'
+            }
+        }).should.equal('...String');
+    });
+
+    it('NonNullableType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.NonNullableType,
+            expression: {
+                type: doctrine.Syntax.NameExpression,
+                name: 'String'
+            },
+            prefix: true
+        }).should.equal('!String');
+
+        doctrine.type.stringify({
+            type: doctrine.Syntax.NonNullableType,
+            expression: {
+                type: doctrine.Syntax.NameExpression,
+                name: 'String'
+            },
+            prefix: false
+        }).should.equal('String!');
+    });
+
+    it('OptionalType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.OptionalType,
+            expression: {
+                type: doctrine.Syntax.NameExpression,
+                name: 'String'
+            }
+        }).should.equal('String=');
+    });
+
+    it('NullableType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.NullableType,
+            expression: {
+                type: doctrine.Syntax.NameExpression,
+                name: 'String'
+            },
+            prefix: true
+        }).should.equal('?String');
+
+        doctrine.type.stringify({
+            type: doctrine.Syntax.NullableType,
+            expression: {
+                type: doctrine.Syntax.NameExpression,
+                name: 'String'
+            },
+            prefix: false
+        }).should.equal('String?');
+    });
+
+    it('TypeApplication', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.TypeApplication,
+            expression: {
+                type: doctrine.Syntax.NameExpression,
+                name: 'Array'
+            },
+            applications: [
+                {
+                    type: doctrine.Syntax.NameExpression,
+                    name: 'String'
+                }
+            ]
+        }).should.equal('Array.<String>');
+
+        doctrine.type.stringify({
+            type: doctrine.Syntax.TypeApplication,
+            expression: {
+                type: doctrine.Syntax.NameExpression,
+                name: 'Array'
+            },
+            applications: [
+                {
+                    type: doctrine.Syntax.NameExpression,
+                    name: 'String'
+                },
+                {
+                    type: doctrine.Syntax.AllLiteral
+                }
+            ]
+        }).should.equal('Array.<String, *>');
+    });
+});
+
+describe('Complex identity', function () {
+    it('Functions', function () {
+        var data01 = 'function (): void';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data01)
+        ).should.equal(data01);
+
+        var data02 = 'function (): String';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data02)
+        ).should.equal(data02);
+
+        var data03 = 'function (test: string): String';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data03)
+        ).should.equal(data03);
+
+        var data04 = 'function (this: Date, test: String): String';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data04)
+        ).should.equal(data04);
+
+        var data05 = 'function (this: Date, a: String, b: Number): String';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data05)
+        ).should.equal(data05);
+
+        var data06 = 'function (this: Date, a: Array.<String, Number>, b: Number): String';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data06)
+        ).should.equal(data06);
+
+        var data07 = 'function (new: Date, a: Array.<String, Number>, b: Number): HashMap.<String, Number>';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data07)
+        ).should.equal(data07);
+
+        var data08 = 'function (new: Date, a: Array.<String, Number>, b: (Number|String|Date)): HashMap.<String, Number>';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data08)
+        ).should.equal(data08);
+
+        var data09 = 'function (new: Date)';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data09)
+        ).should.equal(data09);
+
+        var data10 = 'function (this: Date)';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data10)
+        ).should.equal(data10);
+
+        var data11 = 'function (this: Date, ...list)';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data11)
+        ).should.equal(data11);
+
+        var data11a = 'function (this: Date, test: String=)';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data11a)
+        ).should.equal(data11a);
+
+        // raw ... are not supported
+//        var data12 = 'function (this: Date, ...)';
+//        doctrine.type.stringify(
+//            doctrine.type.parseType(data12)
+//        ).should.equal(data12);
+
+        var data12a = 'function (this: Date, ?=)';
+        doctrine.type.stringify(
+            doctrine.type.parseType(data12a)
+        ).should.equal(data12a);
+    });
+});
+
+/* vim: set sw=4 ts=4 et tw=80 : */
diff --git a/test/test.html b/test/test.html
new file mode 100644
index 0000000..3784c2e
--- /dev/null
+++ b/test/test.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>Doctrine trying</title>
+<script src="../doctrine.js"></script>
+<script type="text/javascript">
+	window.onload = function() {
+		document.getElementById("doit").onclick = function() {
+			var res = doctrine.parseParamType(document.getElementById("commenttext").value,
+				{unwrap:true, recoverable:true} );
+				
+			document.getElementById("parse_tree_res").innerHTML = JSON.stringify(res, null, ' ');
+			document.getElementById("stringify_res").textContent = doctrine.stringify(res);
+		};
+	};
+</script>
+</head>
+<body>
+	<textarea id="commenttext">Add a jsdoc comment here</textarea>
+	<br/>
+	<br/>
+	<button id="doit">Click to parse</button>
+	<br/>
+	<br/>
+	<h3>Parse Tree</h3>
+	<pre id="parse_tree_res" ></pre>
+	<h3>Stringified</h3>
+	<pre id="stringify_res" ></pre>
+</body>
+</html>
\ No newline at end of file
diff --git a/test/unwrap.js b/test/unwrap.js
new file mode 100644
index 0000000..1ddfbcb
--- /dev/null
+++ b/test/unwrap.js
@@ -0,0 +1,41 @@
+/*
+ * @fileoverview Main Doctrine object
+ * @author Yusuke Suzuki <utatane.tea at gmail.com>
+ */
+
+/*jslint node:true */
+'use strict';
+
+var fs = require('fs'),
+    path = require('path'),
+    root = path.join(path.dirname(fs.realpathSync(__filename)), '..'),
+    doctrine = require(root);
+require('should');
+
+describe('unwrapComment', function () {
+  it('normal', function () {
+    doctrine.unwrapComment('/**\n * @const\n * @const\n */').should.equal('\n at const\n at const');
+  });
+
+  it('single', function () {
+    doctrine.unwrapComment('/**x*/').should.equal('x');
+  });
+
+  it('more stars', function () {
+    doctrine.unwrapComment('/***x*/').should.equal('x');
+    doctrine.unwrapComment('/****x*/').should.equal('*x');
+  });
+
+  it('2 lines', function () {
+    doctrine.unwrapComment('/**x\n * y\n*/').should.equal('x\ny');
+  });
+
+  it('2 lines with space', function () {
+    doctrine.unwrapComment('/**x\n *    y\n*/').should.equal('x\n   y');
+  });
+
+  it('3 lines with blank line', function () {
+    doctrine.unwrapComment('/**x\n *\n \* y\n*/').should.equal('x\n\ny');
+  });
+});
+/* vim: set sw=4 ts=4 et tw=80 : */

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-doctrine.git



More information about the Pkg-javascript-commits mailing list