This commit is contained in:
watrabi
2025-09-18 17:55:52 -04:00
commit 977f1ff4b8
15030 changed files with 17324420 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
# development-related files
.eslintignore
.eslintrc
.gitignore
.travis.yml
gulpfile.js
# scripts for launching JSDoc with Mozilla Rhino
/jsdoc*
!/jsdoc.js
# Rhino and test directories
rhino/
test/
@@ -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.
+69
View File
@@ -0,0 +1,69 @@
Pull Requests
-------------
If you're thinking about making some changes, maybe fixing a bug, or adding a
snazzy new feature, first, thank you. Contributions are very welcome. Things
need to be manageable for the maintainers, however. So below you'll find **The
fastest way to get your pull request merged in.** Some things, particularly how
you set up your branches and work with git, are just suggestions, but pretty good
ones.
1. **Create a remote to track the base jsdoc3/jsdoc repository**
This is just a convenience to make it easier to update your ```<tracking branch>```
(more on that shortly). You would execute something like:
git remote add base git://github.com/jsdoc3/jsdoc.git
Here 'base' is the name of the remote. Feel free to use whatever you want.
2. **Set up a tracking branch for the base repository**
We're gonna call this your ```<tracking branch>```. You will only ever update
this branch by pulling from the 'base' remote. (as opposed to 'origin')
git branch --track pullpost base/master
git checkout pullpost
Here 'pullpost' is the name of the branch. Fell free to use whatever you want.
3. **Create your change branch**
Once you are in ```<tracking branch>```, make sure it's up to date, then create
a branch for your changes off of that one.
git branch fix-for-issue-395
git checkout fix-for-issue-395
Here 'fix-for-issue-395' is the name of the branch. Feel free to use whatever
you want. We'll call this the ```<change branch>```. This is the branch that
you will eventually issue your pull request from.
The purpose of these first three steps is to make sure that your merge request
has a nice clean diff that only involves the changes related to your fix/feature.
4. **Make your changes**
On your ```<change branch>``` make any changes relevant to your fix/feature. Don't
group fixes for multiple unrelated issues or multiple unrelated features together.
Create a separate branch for each unrelated changeset. For instance, if you're
fixing a bug in the parser and adding some new UI to the default template, those
should be separate branches and merge requests.
5. **Add tests**
Add tests for your change. If you are submitting a bugfix, include a test that
verifies the existence of the bug along with your fix. If you are submitting
a new feature, include tests that verify proper feature function, if applicable.
See the readme in the 'test' directory for more information
6. **Commit and publish**
Commit your changes and publish your branch (or push it if it's already published)
7. **Issue your pull request**
On github.com, switch to your ```<change branch>``` and click the 'Pull Request'
button. Enter some meaningful information about the pull request. If it's a bugfix,
that doesn't already have an issue associated with it, provide some info on what
situations that bug occurs in and a sense of it's severity. If it does already have
an issue, make sure the include the hash and issue number (e.g. '#100') so github
links it.
If it's a feature, provide some context about the motivations behind the feature,
why it's important/useful/cool/necessary and what it does/how it works. Don't
worry about being too verbose. Folks will be much more amenable to reading through
your code if they know what its supposed to be about.
+380
View File
@@ -0,0 +1,380 @@
# License #
JSDoc 3 is free software, licensed under the Apache License, Version 2.0 (the
"License"). Commercial and non-commercial use are permitted in compliance with
the License.
Copyright (c) 2011-2014 Michael Mathews <micmath@gmail.com> and the
[contributors to JSDoc](https://github.com/jsdoc3/jsdoc/graphs/contributors).
All rights reserved.
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
In addition, a copy of the License is included with this distribution.
As stated in Section 7, "Disclaimer of Warranty," of the License:
> 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.
The source code for JSDoc 3 is available at:
https://github.com/jsdoc3/jsdoc
# Third-Party Software #
JSDoc 3 includes or depends upon the following third-party software, either in
whole or in part. Each third-party software package is provided under its own
license.
## MIT License ##
Several of the following software packages are distributed under the MIT
license, which is reproduced below:
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
## Acorn ##
Portions of the Acorn source code are incorporated into the following files:
- `lib/jsdoc/src/walker.js`
Acorn is distributed under the MIT license, which is reproduced above.
Copyright (C) 2012 Marijn Haverbeke <marijnh@gmail.com>.
The source code for Acorn is available at:
https://github.com/marijnh/acorn
## Async.js ##
Async.js is distributed under the MIT license, which is reproduced above.
Copyright (c) 2010 Caolan McMahon.
The source code for Async.js is available at:
https://github.com/caolan/async
## Catharsis ##
Catharsis is distributed under the MIT license, which is reproduced above.
Copyright (c) 2012-2014 Jeff Williams.
The source code for Catharsis is available at:
https://github.com/hegemonic/catharsis
## crypto-browserify ##
crypto-browserify is distributed under the MIT license, which is reproduced
above.
Copyright (c) 2013 Dominic Tarr.
The source code for crypto-browserify is available at:
https://github.com/dominictarr/crypto-browserify
## escape-string-regexp ##
escape-string-regexp is distributed under the MIT License, which is reproduced
above.
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com>.
The source code for escape-string-regexp is available at:
https://github.com/sindresorhus/escape-string-regexp
## Esprima ##
Esprima is distributed under the BSD 2-clause license:
> 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.
Copyright (c) 2011-2013 Ariya Hidayat and other Esprima contributors.
The source code for Esprima is available at:
https://github.com/ariya/esprima
## events ##
Portions of the events source code are incorporated into the following files:
+ `rhino/events.js`
events is distributed under the MIT license, which is reproduced above.
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
The source code for events is available at:
https://github.com/Gozala/events
## github-flavored-markdown ##
github-flavored-markdown is distributed under the BSD 3-clause license:
> Copyright (c) 2007, John Fraser <http://www.attacklab.net/> All rights
> reserved.
>
> Original Markdown copyright (c) 2004, John Gruber <http://daringfireball.net/>
> All rights reserved.
>
> 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.
> - Neither the name "Markdown" nor the names of its contributors may be used
> to endorse or promote products derived from this software without specific
> prior written permission.
>
> 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 the copyright owner or contributors 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.
The source code for github-flavored-markdown is available at:
https://github.com/hegemonic/github-flavored-markdown
## Google Code Prettify ##
Google Code Prettify is distributed under the Apache License 2.0, which is
included with this package.
Copyright (c) 2006 Google Inc.
The source code for Google Code Prettify is available at:
https://code.google.com/p/google-code-prettify/
## Jasmine ##
Jasmine is distributed under the MIT license, which is reproduced above.
Copyright (c) 2008-2011 Pivotal Labs.
The source code for Jasmine is available at:
https://github.com/pivotal/jasmine
## jasmine-node ##
jasmine-node is distributed under the MIT license, which is reproduced above.
Copyright (c) 2010 Adam Abrons and Misko Hevery (http://getangular.com).
The source code for jasmine-node is available at:
https://github.com/mhevery/jasmine-node
## js2xmlparser ##
js2xmlparser is distributed under the MIT license, which is reproduced above.
Copyright (c) 2012 Michael Kourlas.
The source code for js2xmlparser is available at:
https://github.com/michaelkourlas/node-js2xmlparser
## Node.js ##
Portions of the Node.js source code are incorporated into the following files:
- `rhino/fs.js`
- `rhino/path.js`
- `rhino/querystring.js`
- `rhino/util.js`
Node.js is distributed under the MIT license, which is reproduced above.
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
The source code for Node.js is available at:
https://github.com/joyent/node
## node-browser-builtins ##
Portions of the node-browser-builtins source code are incorporated into the
following files:
- `rhino/assert.js`
- `rhino/rhino-shim.js`
node-browser-builtins is distributed under the MIT license, which is reproduced
above.
The source code for node-browser-builtins is available at:
https://github.com/alexgorbatchev/node-browser-builtins
## Open Sans ##
Open Sans is distributed under the Apache License 2.0, which is
included with this package.
Copyright (c) 2010-2011, Google Inc.
This typeface, including the complete set of variations, are available at:
http://www.google.com/fonts/specimen/Open+Sans
## Requizzle ##
Requizzle is distributed under the MIT license, which is reproduced above.
Copyright (c) 2014 Google Inc. All rights reserved.
Copyright (c) 2012-2013 Johannes Ewald.
The source code for Requizzle is available at:
https://github.com/hegemonic/requizzle
## Rhino ##
Rhino is distributed under the following licenses:
### MPL 2.0 License ###
The majority of the source code for Rhino is available under the Mozilla Public
License (MPL) 2.0, which is included in this distribution.
### License for portions of the Rhino debugger ###
Additionally, some files are available under the BSD 3-clause license:
> Copyright 1997, 1998 Sun Microsystems, Inc. All Rights Reserved.
>
> 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.
> - Neither the name of Sun Microsystems nor the names of its contributors
> may be used to endorse or promote products derived from this software
> without specific prior written permission.
>
> 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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
### Source Code ###
The source code for Rhino is available at:
https://github.com/jsdoc3/rhino
## TaffyDB ##
TaffyDB is distributed under a modified BSD license:
> All rights reserved.
>
> Redistribution and use of this software in source and binary forms, with or
> without modification, are permitted provided that the following condition is
> met:
>
> Redistributions of source code must retain the above copyright notice, this
> list of conditions and the following disclaimer.
>
> 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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
The source code for TaffyDB is available at:
https://github.com/hegemonic/taffydb
## Tomorrow Theme for Google Code Prettify ##
License information for the Tomorrow Theme for Google Code Prettify is not
available. It is assumed that the package is distributed under an open source
license that is compatible with the Apache License 2.0.
Copyright (c) Yoshihide Jimbo.
The source code for the Tomorrow Theme is available at:
https://github.com/jmblog/color-themes-for-google-code-prettify
## tv4 ##
tv4 is in the public domain. It is also distributed under the MIT license, which
is reproduced above.
The source code for tv4 is available at:
https://github.com/geraintluff/tv4
## Underscore.js ##
Underscore.js is distributed under the MIT license, which is reproduced above.
Copyright (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative
Reporters & Editors.
The source code for Underscore.js is available at:
https://github.com/jashkenas/underscore
## wrench-js ##
wrench-js is distributed under the MIT license, which is reproduced above.
Copyright (c) 2010 Ryan McGrath.
The source code for wrench-js is available at:
https://github.com/ryanmcgrath/wrench-js
+135
View File
@@ -0,0 +1,135 @@
JSDoc 3
=======
[![Build Status](https://img.shields.io/travis/jsdoc3/jsdoc.svg)](http://travis-ci.org/jsdoc3/jsdoc)
An API documentation generator for JavaScript.
Want to contribute to JSDoc? Please read `CONTRIBUTING.md`.
Installation and Usage
----------------------
You can run JSDoc on either Node.js or Mozilla Rhino.
### Node.js
Native support for Node.js is available in JSDoc 3.3.0 and later. JSDoc
supports Node.js 0.10 and later.
#### Installing JSDoc for Node.js
You can install JSDoc in your project's `node_modules` folder, or you can
install it globally.
To install the latest alpha version:
npm install jsdoc@"<=3.3.0"
To install the latest development version:
npm install git+https://github.com/jsdoc3/jsdoc.git
#### Running JSDoc with Node.js
If you installed JSDoc locally, the JSDoc command-line tool is available in
`./node_modules/.bin`. To generate documentation for the file
`yourJavaScriptFile.js`:
./node_modules/.bin/jsdoc yourJavaScriptFile.js
Or if you installed JSDoc globally, simply run the `jsdoc` command:
jsdoc yourJavaScriptFile.js
By default, the generated documentation is saved in a directory named `out`. You
can use the `--destination` (`-d`) option to specify another directory.
Run `jsdoc --help` for a complete list of command-line options.
### Mozilla Rhino
All versions of JSDoc 3 run on a customized version of Mozilla Rhino, which
requires Java. You can run JSDoc 3 on Java 1.6 and later.
#### Installing JSDoc for Mozilla Rhino
To install JSDoc, download a .zip file for the
[latest development version](https://github.com/jsdoc3/jsdoc/archive/master.zip)
or a [previous release](https://github.com/jsdoc3/jsdoc/tags).
You can also use git to clone the
[JSDoc repository](https://github.com/jsdoc3/jsdoc):
git clone git+https://github.com/jsdoc3/jsdoc.git
The JSDoc repository includes a
[customized version of Mozilla Rhino](https://github.com/jsdoc3/rhino). Make
sure your Java classpath does not include any other versions of Rhino. (On OS X,
you may need to remove the file `~/Library/Java/Extensions/js.jar`.)
**Note**: In JSDoc 3.3.0 and later, if you need to run JSDoc on Mozilla Rhino,
do not install JSDoc with npm. Use one of the methods described above.
#### Running JSDoc with Mozilla Rhino
On OS X, Linux, and other POSIX systems, to generate documentation for the file
`yourJavaScriptFile.js`:
./jsdoc yourJavaScriptFile.js
Or on Windows:
jsdoc yourJavaScriptFile.js
By default, the generated documentation is saved in a directory named `out`. You
can use the `--destination` (`-d`) option to specify another directory.
Run `jsdoc --help` for a complete list of command-line options.
Templates and Build Tools
-------------------------
The JSDoc community has created numerous templates and other tools to help you
generate and customize your documentation. Here are just a few:
### Templates
+ [jaguarjs-jsdoc](https://github.com/davidshimjs/jaguarjs-jsdoc)
([example](http://davidshimjs.github.io/jaguarjs/doc))
+ [DocStrap](https://github.com/terryweiss/docstrap)
+ [jsdoc3Template](https://github.com/DBCDK/jsdoc3Template)
([example](https://github.com/danyg/jsdoc3Template/wiki#wiki-screenshots))
### Build Tools
+ [JSDoc Grunt plugin](https://github.com/krampstudio/grunt-jsdoc)
+ [JSDoc ant task](https://github.com/jannon/jsdoc3-ant-task)
### Generating Typeface Fonts
JSDoc 3 uses the [OpenSans](https://www.google.com/fonts/specimen/Open+Sans) typeface, the fonts for which can be re-generated as follows:
1. Open the [OpenSans page at Font Squirrel](<http://www.fontsquirrel.com/fonts/open-sans>).
2. Click on the 'Webfont Kit' tab.
3. Either leave the subset drop-down as 'Western Latin (Default)', or if we decide we need more glyphs than change it to 'No Subsetting'.
4. Click the 'DOWNLOAD @FONT-FACE KIT' button.
5. For each typeface variant we plan to use, copy the 'eot', 'svg' and 'woff' files into the 'templates/default/static/fonts' directory.
For More Information
--------------------
+ Documentation is available at [Use JSDoc](http://usejsdoc.org).
+ Contribute to the docs at [jsdoc3/jsdoc3.github.com](https://github.com/jsdoc3/jsdoc3.github.com).
+ Ask for help on the [JSDoc Users mailing list](http://groups.google.com/group/jsdoc-users).
+ Post questions tagged `jsdoc` to [Stack
Overflow](http://stackoverflow.com/questions/tagged/jsdoc).
License
-------
JSDoc 3 is copyright (c) 2011-2014 Michael Mathews <micmath@gmail.com> and the
[contributors to JSDoc](https://github.com/jsdoc3/jsdoc/graphs/contributors).
JSDoc 3 is free software, licensed under the Apache License, Version 2.0. See
the file `LICENSE.md` in this distribution for more details.
+240
View File
@@ -0,0 +1,240 @@
# JSDoc 3 change history
This file describes notable changes in each version of JSDoc 3. To download a specific version of JSDoc 3, see [GitHub's tags page](https://github.com/jsdoc3/jsdoc/tags).
## 3.2.2 (November 2013)
### Bug fixes
+ Addressed a regression in JSDoc 3.2.1 that could prevent a function declaration from shadowing a declaration with the same name in an outer scope. (#513)
+ If a child class overrides a method in a parent class without documenting the overridden method, the method's documentation is now copied from the parent class. (#503)
+ You can now use inline HTML tags in Markdown-formatted text. In addition, JSDoc now uses only the [marked Markdown parser](https://github.com/chjj/marked); the markdown-js parser has been removed. (#510)
+ Type expressions can now include a much broader range of repeatable types. In addition, you can now use Closure Compiler's nullable and non-nullable modifiers with repeatable types. For example, the type expression `...!string` (a repeatable, non-nullable string) is now parsed correctly. (#502)
+ If a function accepts a parameter named `prototype`, the parameter is no longer renamed during parsing. (#505)
+ If the list of input files includes relative paths, the paths are now resolved relative to the user's working directory. (a3d33842)
## 3.2.1 (October 2013)
### Enhancements
+ JSDoc's parser now fires a `processingComplete` event after JSDoc has completed all post-processing of the parse results. This event has a `doclets` property containing an array of doclets. (#421)
+ When JSDoc's parser fires a `parseComplete` event, the event now includes a `doclets` property containing an array of doclets. (#431)
+ You can now use relative paths in the JSDoc configuration file's `source.exclude` option. Relative paths will be resolved relative to the current working directory. (#405)
+ If a symbol uses the `@default` tag, and its default value is an object literal, this value is now stored as a string, and the doclet will have a `defaultvaluetype` property containing the string `object`. This change enables templates to show the default value with appropriate syntax highlighting. (#419)
+ Inline `{@link}` tags can now contain newlines. (#441)
### Bug fixes
+ Inherited symbols now indicate that they were inherited from the ancestor that defined the symbol, rather than the direct parent. (#422)
+ If the first line of a JavaScript file contains a hashbang (for example, `#!/usr/bin/env node`), the hashbang is now ignored when the file is parsed. (#499)
+ Resolved a crash when a JavaScript file contains a [JavaScript 1.8](https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/1.8) keyword, such as `let`. (#477)
+ The type expression `function[]` is now parsed correctly. (#493)
+ If a module is tagged incorrectly, the module's output file now has a valid filename. (#440, #458)
+ For tags that accept names, such as `@module` and `@param`, if a hyphen is used to separate the name and description, the hyphen must appear on the same line as the name. This change prevents a Markdown bullet on the followng line from being interpreted as a separator. (#459)
+ When lenient mode is enabled, a `@param` tag with an invalid type expression no longer causes a crash. (#448)
+ The `@requires` tag can now contain an inline tag in its tag text. (#486)
+ The `@returns` tag can now contain inline tags even if a type is not specified. (#444)
+ When lenient mode is enabled, a `@returns` tag with no value no longer causes a crash. (#451)
+ The `@type` tag now works correctly with type expressions that span multiple lines. (#427)
+ If a string contains inline `{@link}` tags preceded by bracketed link text (for example, `[test]{@link Test#test}`), HTML links are now generated correctly even if the string contains other bracketed text. (#470)
+ On POSIX systems, if you run JSDoc using a symlink to the startup script, JSDoc now works correctly. (#492)
### Default template
+ Pretty-printed source files are now generated by default. To disable this feature, add the property `templates.default.outputSourceFiles: false` to your `conf.json` file. (#454)
+ Links to a specific line in a source file now work correctly. (#475)
+ Pretty-printed source files are now generated using the encoding specified in the `-e/--encoding` option. (#496)
+ If a `@default` tag is added to a symbol whose default value is an object, the value is now displayed in the output file. (#419)
+ Output files now identify symbols as "abstract" rather than "virtual." (#432)
## 3.2.0 (May 2013)
### Major changes
+ JSDoc can now parse any valid [Google Closure Compiler type expression](https://developers.google.com/closure/compiler/docs/js-for-compiler#types). **Note**: As a result of this change, JSDoc quits if a file contains an invalid type expression. To prevent JSDoc from quitting, run JSDoc with the `--lenient` (`-l`) command-line option. (Multiple issues)
+ You can now use the new `@listens` tag to indicate that a symbol listens for an event. (#273)
### Enhancements
+ The parser now fires a `parseBegin` event before it starts parsing files, as well as a `parseComplete` event after all files have been parsed. Plugins can define event handlers for these events, and `parseBegin` handlers can modify the list of files to parse. (#299)
+ Event handlers for `jsdocCommentFound` events can now modify the JSDoc comment. (#228)
+ You can now exclude tags from Markdown processing using the new option `markdown.excludeTags` in the configuration file. (#337)
+ You can now use the [marked](https://github.com/chjj/marked) Markdown parser by setting the configuration property `markdown.parser` to `marked`. In addition, if `markdown.parser` is set to `gfm`, JSDoc will now use the "marked" parser instead. (#385)
+ The `@typedef` tag no longer requires a name when used with a Closure Compiler-style type definition. For example, the following type definition will automatically get the name `Foo.Bar`:
```javascript
/** @typedef {string} */
Foo.Bar;
```
(#391)
+ You can now use an inline `{@type}` tag in a parameter's description. If this tag is present, JSDoc will assume that the parameter uses the type specified in the inline `{@type}` tag. For example, the following `@param` tag would cause `myParam`'s type to be documented as `Foo`:
```
@param {(boolean|string)} myParam - My special parameter. {@type Foo}
```
(#152)
+ The `console.log` function now behaves the same way as on Node.js. In addition, the functions `console.info`, `console.error`, `console.warn`, and `console.trace` have been implemented. (#298)
+ You can now use npm to install JSDoc globally by running `npm install -g`. **Note**: JSDoc will still run under Mozilla Rhino, not Node.js. (#374)
+ The `jsVersion` configuration property has been removed. (#390)
### Bug fixes
+ JSDoc now quits if the configuration file cannot be loaded. (#407)
+ JSDoc's `--explain` (`-X`) option now runs much more quickly, and it outputs valid JSON to the console. (#298)
+ JSDoc's `--lenient` (`-l`) option now prints warnings on STDERR rather than STDOUT. (#298)
+ The parser now assigns the correct scope to object properties whose names include single quotes. (#386)
+ The parser now recognizes CommonJS modules that export a single function rather than an object. (#384)
+ The inline `{@link}` tag now works correctly when `@link` is followed by a tab. (#359)
+ On POSIX systems, quoted command-line arguments are no longer split on spaces. (#397)
### Plugins
+ The new `overloadHelper` plugin makes it easier to link to overloaded methods. (#179)
+ The `markdown` plugin now converts Markdown links in the `@see` tag. (#297)
### Default template enhancements
+ You can now use the configuration property `templates.default.staticFiles` to copy additional static files to the output directory. (#393)
+ All output files now use human-readable filenames. (#339)
+ The documentation for events now lists the symbols that listen to that event. (#273)
+ Links to source files now allow you to jump to the line where a symbol is defined. (#316)
+ The output files now link to individual types within a Closure Compiler type expression. (Multiple issues)
+ CommonJS modules that export a single function, rather than an object, are now documented more clearly. (#384)
+ Functions that can throw multiple types of errors are now documented more clearly. (#389)
+ If a `@property` tag does not identify the property's name, the template no longer throws an error. (#373)
+ The type of each `@typedef` is now displayed. (#391)
+ If a `@see` tag contains a URL (for example, `@see http://example.com` or `@see <http://example.com>`), the tag text is now converted to a link. (#371)
+ Repeatable parameters are now identified. (#381)
+ The "Classes" header is no longer repeated in the navigation bar. (#361)
+ When the only documented symbols in global scope are type definitions, you can now click the "Global" header to view their documentation. (#261)
## 3.1.1 (February 2013)
+ Resolved a crash when no input files contain JSDoc comments. (#329)
+ Resolved a crash when JSDoc cannot identify the common prefix of several paths. (#330)
+ Resolved a crash when the full path to JSDoc contained at least one space. (#347)
+ Files named `README.md` or `package.json` will now be processed when they are specified on the command line. (#350)
+ You can now use `@emits` as a synonym for `@fires`. (#324)
+ The module `jsdoc/util/templateHelper` now allows you to specify the CSS class for links that are generated by the following methods: (#331)
+ `getAncestorLinks`
+ `getSignatureReturns`
+ `getSignatureTypes`
+ `linkto`
## 3.1.0 (January 2013)
### Major changes
+ You can now use the new `@callback` tag to provide information about a callback function's signature. To document a callback function, create a standalone JSDoc comment, as shown in the following example:
```javascript
/**
* @class
*/
function MyClass() {}
/**
* Send a request.
*
* @param {MyClass~responseCb} cb - Called after a response is received.
*/
MyClass.prototype.sendRequest = function(cb) {
// code
};
/**
* Callback for sending a request.
*
* @callback MyClass~responseCb
* @param {?string} error - Information about the error.
* @param {?string} response - Body of the response.
*/
```
+ The inline link tag, `{@link}`, has been improved:
+ You can now use a space as the delimiter between the link target and link text.
+ In your `conf.json` file, you can now enable the option `templates.cleverLinks` to display code links in a monospace font and URL links in plain text. You can also enable the option `templates.monospaceLinks` to display all links in a monospace font. **Note**: JSDoc templates must be updated to respect these options.
+ You can now use the new inline tags `{@linkplain}`, which forces a plain-text link, and `{@linkcode}`, which forces a monospace link. These tags always override the settings in your `conf.json` file. (#250)
+ JSDoc now provides a `-l/--lenient` option that tells JSDoc to continue running if it encounters a non-fatal error. (Multiple issues)
+ A template's `publish.js` file should now assign its `publish` function to `exports.publish`, rather than defining a global `publish` function. The global `publish` function is deprecated and may not be supported in future versions. JSDoc's built-in templates reflect this change. (#166)
+ The template helper (`templateHelper.js`) exports a variety of new functions for finding information within a parse tree. These functions were previously contained within the default template. (#186)
+ Updated the `fs` and `path` modules to make their behavior more consistent with Node.js. In addition, created extended versions of these modules with additional functionality. (Multiple commits)
+ Updated or replaced numerous third-party modules. (Multiple commits)
+ Reorganized the JSDoc codebase in preparation for future enhancements. (Multiple commits)
+ JSDoc now embeds a version of Mozilla Rhino that recognizes Node.js packages, including `package.json` files. (Multiple commits)
+ Node.js' `npm` utility can now install JSDoc from its GitHub repository. **Note**: JSDoc is not currently compatible with Node.js. However, this change allows JSDoc to be installed as a dependency of a Node.js project. In this version, global installation with `npm` is not supported. (Multiple commits)
### Enhancements
+ If a `README.md` file is passed to JSDoc, its contents will be included on the `index.html` page of the generated documentation. (#128)
+ The `@augments` tag can now refer to an undocumented member, such as `window.XMLHTTPRequest`. (#160)
+ The `@extends` tag can now refer to an undocumented member, such as `window.XMLHttpRequest`. In addition, you can now use `@host` as a synonym for `@extends`. (#145)
+ The `@lends` tag is now supported in multiline JSDoc comments. (#163)
+ On Windows, `jsdoc.cmd` now provides the same options as the `jsdoc` shell script. (#127)
+ JSDoc now provides `setTimeout()`, `clearTimeout()`, `setInterval()`, and `clearInterval()` functions. (Multiple commits)
+ JSDoc no longer provides a global `exit()` function. Use `process.exit()` instead. (1228a8f7)
+ JSDoc now includes additional shims for Node.js' built-in modules. **Note**: Many of these shims implement only the functions that JSDoc uses, and they may not be consistent with Node.js' behavior in edge cases. (Multiple commits)
+ JSDoc now provides a `-v/--version` option to display information about the current version. (#303)
+ When running tests, you can now use the `--nocolor` option to disable colored output. On Windows, colored output is always disabled. (e17601fe, 8bc33541)
### Bug fixes
+ When using the `@event` tag to define an event within a class or namespace, the event's longname is now set correctly regardless of tag order. (#280)
+ The `@property` tag no longer results in malformed parse trees. (20f87094)
+ The `jsdoc` and `jsdoc.cmd` scripts now work correctly with paths that include spaces. (#127, #130)
+ The `jsdoc` script now works correctly on Cygwin and MinGW, and with the `dash` shell. (#182, #184, #187)
+ The `-d/--destination` option is no longer treated as a path relative to the JSDoc directory. Instead, it can contain an absolute path, or a path relative to the current working directory. (f5e3f0f3)
+ JSDoc now provides default options for the values in `conf.json`. (#129)
+ If the `conf.json` file does not exist, JSDoc no longer tries to create it, which prevents errors if the current user does not have write access to the JSDoc directory. (d2d05fcb)
+ Doclets for getters and setters are now parsed appropriately. (#150)
+ Only the first asterisk is removed from each line of a JSDoc comment. (#172)
+ If a child member overrides an ancestor member, the ancestor member is no longer documented. (#158)
+ If a member of a namespace has the same name as a namespace, the member is now documented correctly. (#214)
+ The parse tree now uses a single set of properties to track both JSDoc-style type information and Closure Compiler-style type information. (#118)
+ If a type has a leading `!`, indicating that it is non-nullable, the leading `!` is now removed from the type name. (#226)
+ When Markdown formatting is enabled, underscores in inline `{@link}` tags are no longer treated as Markdown formatting characters. (#259)
+ Markdown links now work correctly when a JavaScript reserved word, such as `constructor`, is used as the link text. (#249)
+ Markdown files for tutorials are now parsed based on the settings in `conf.json`, rather than using the "evilstreak" Markdown parser in all cases. (#220)
+ If a folder contains both tutorial source files and `.js` files, JSDoc no longer attempts to parse the `.js` files as JSON files. (#222)
+ The "evilstreak" Markdown parser now works correctly with files that use Windows-style line endings. (#223)
+ JSDoc no longer fails unit tests when the `conf.json` file is not present. (#206)
+ On Windows, JSDoc now passes all unit tests. (Multiple commits)
### Plugins
+ The new `partial` plugin adds support for a `@partial` tag, which links to an external file that contains JSDoc comments. (#156)
+ The new `commentsOnly` plugin removes everything in a file except JSDoc-style comments. You can use this plugin to document source files that are not valid JavaScript, including source files for other languages. (#304)
+ The new `eventDumper` plugin logs information about parser events to the console. (#242)
+ The new `verbose` plugin logs the name of each input file to the console. (#157)
### Template enhancements
#### Default template
+ The template output now includes pretty-printed versions of source files. This feature is enabled by default. To disable this feature, add the property `templates.default.outputSourceFiles: false` to your `conf.json` file. (#208)
+ You can now use the template if it is placed outside of the JSDoc directory. (#198)
+ The template no longer throws an error when a parameter does not have a name. (#175)
+ The navigation bar now includes an "Events" section if any events are documented. (#280)
+ Pages no longer include a "Classes" header when no classes are documented. (eb0186b9)
+ Member details now include "Inherited From" section when a member is inherited from another member. (#154)
+ If an `@author` tag contains text in the format "Jane Doe <jdoe@example.com>", the value is now converted to an HTML `mailto:` link. (#326)
+ Headings for functions now include the function's signature. (#253)
+ Type information is now displayed for events. (#192)
+ Functions now link to their return type when appropriate. (#192)
+ Type definitions that contain functions are now displayed correctly. (#292)
+ Tutorial output is now generated correctly. (#188)
+ Output files now use Google Code Prettify with the Tomorrow theme as a syntax highlighter. (#193)
+ The `index.html` output file is no longer overwritten if a namespace called `index` has been documented. (#244)
+ The current JSDoc version number is now displayed in the footer. (#321)
#### Haruki template
+ Members are now contained in arrays rather than objects, allowing overloaded members to be documented. (#153)
+ A clearer error message is now provided when the output destination is not specified correctly. (#174)
## 3.0.1 (June 2012)
### Enhancements
+ The `conf.json` file may now contain `source.include` and `source.exclude` properties. (#56)
+ `source.include` specifies files or directories that JSDoc should _always_ check for documentation.
+ `source.exclude` specifies files or directories that JSDoc should _never_ check for documentation.
These settings take precedence over the `source.includePattern` and `source.excludePattern` properties, which contain regular expressions that JSDoc uses to search for source files.
+ The `-t/--template` option may now specify the absolute path to a template. (#122)
### Bug fixes
+ JSDoc no longer throws exceptions when a symbol has a special name, such as `hasOwnProperty`. (1ef37251)
+ The `@alias` tag now works correctly when documenting inner classes as globals. (810dd7f7)
### Template improvements
+ The default template now sorts classes by name correctly when the classes come from several modules. (4ce17195)
+ The Haruki template now correctly supports `@example`, `@members`, and `@returns` tags. (6580e176, 59655252, 31c8554d)
## 3.0.0 (May 2012)
Initial release.
+468
View File
@@ -0,0 +1,468 @@
/*global java */
/*eslint no-process-exit:0 */
/**
* Helper methods for running JSDoc on the command line.
*
* A few critical notes for anyone who works on this module:
*
* + The module should really export an instance of `cli`, and `props` should be properties of a
* `cli` instance. However, Rhino interpreted `this` as a reference to `global` within the
* prototype's methods, so we couldn't do that.
* + On Rhino, for unknown reasons, the `jsdoc/fs` and `jsdoc/path` modules can fail in some cases
* when they are required by this module. You may need to use `fs` and `path` instead.
*
* @private
*/
module.exports = (function() {
'use strict';
var logger = require('jsdoc/util/logger');
var stripJsonComments = require('strip-json-comments');
var hasOwnProp = Object.prototype.hasOwnProperty;
var props = {
docs: [],
packageJson: null,
shouldExitWithError: false,
tmpdir: null
};
var app = global.app;
var env = global.env;
var FATAL_ERROR_MESSAGE = 'Exiting JSDoc because an error occurred. See the previous log ' +
'messages for details.';
var cli = {};
// TODO: docs
cli.setVersionInfo = function() {
var fs = require('fs');
var path = require('path');
// allow this to throw--something is really wrong if we can't read our own package file
var info = JSON.parse( fs.readFileSync(path.join(env.dirname, 'package.json'), 'utf8') );
env.version = {
number: info.version,
revision: new Date( parseInt(info.revision, 10) ).toUTCString()
};
return cli;
};
// TODO: docs
cli.loadConfig = function() {
var _ = require('underscore');
var args = require('jsdoc/opts/args');
var Config = require('jsdoc/config');
var fs = require('jsdoc/fs');
var path = require('jsdoc/path');
var confPath;
var isFile;
var defaultOpts = {
destination: './out/',
encoding: 'utf8'
};
try {
env.opts = args.parse(env.args);
}
catch (e) {
console.error(e.message + '\n');
cli.printHelp(function() {
cli.exit(1);
});
}
confPath = env.opts.configure || path.join(env.dirname, 'conf.json');
try {
isFile = fs.statSync(confPath).isFile();
}
catch(e) {
isFile = false;
}
if ( !isFile && !env.opts.configure ) {
confPath = path.join(env.dirname, 'conf.json.EXAMPLE');
}
try {
env.conf = new Config( stripJsonComments(fs.readFileSync(confPath, 'utf8')) )
.get();
}
catch (e) {
cli.exit(1, 'Cannot parse the config file ' + confPath + ': ' + e + '\n' +
FATAL_ERROR_MESSAGE);
}
// look for options on the command line, in the config file, and in the defaults, in that order
env.opts = _.defaults(env.opts, env.conf.opts, defaultOpts);
return cli;
};
// TODO: docs
cli.configureLogger = function() {
function recoverableError() {
props.shouldExitWithError = true;
}
function fatalError() {
cli.exit(1);
}
if (env.opts.debug) {
logger.setLevel(logger.LEVELS.DEBUG);
}
else if (env.opts.verbose) {
logger.setLevel(logger.LEVELS.INFO);
}
if (env.opts.pedantic) {
logger.once('logger:warn', recoverableError);
logger.once('logger:error', fatalError);
}
else {
logger.once('logger:error', recoverableError);
}
logger.once('logger:fatal', fatalError);
return cli;
};
// TODO: docs
cli.logStart = function() {
logger.debug( cli.getVersion() );
logger.debug('Environment info: %j', {
env: {
conf: env.conf,
opts: env.opts
}
});
};
// TODO: docs
cli.logFinish = function() {
var delta;
var deltaSeconds;
if (env.run.finish && env.run.start) {
delta = env.run.finish.getTime() - env.run.start.getTime();
}
if (delta !== undefined) {
deltaSeconds = (delta / 1000).toFixed(2);
logger.info('Finished running in %s seconds.', deltaSeconds);
}
};
// TODO: docs
cli.runCommand = function(cb) {
var cmd;
var opts = env.opts;
function done(errorCode) {
if (!errorCode && props.shouldExitWithError) {
cb(1);
}
else {
cb(errorCode);
}
}
if (opts.help) {
cmd = cli.printHelp;
}
else if (opts.test) {
cmd = cli.runTests;
}
else if (opts.version) {
cmd = cli.printVersion;
}
else {
cmd = cli.main;
}
cmd(done);
};
// TODO: docs
cli.printHelp = function(cb) {
cli.printVersion();
console.log( '\n' + require('jsdoc/opts/args').help() + '\n' );
console.log('Visit http://usejsdoc.org for more information.');
cb(0);
};
// TODO: docs
cli.runTests = function(cb) {
var path = require('jsdoc/path');
var runner = require( path.join(env.dirname, 'test/runner') );
console.log('Running tests...');
runner(function(failCount) {
cb(failCount);
});
};
// TODO: docs
cli.getVersion = function() {
return 'JSDoc ' + env.version.number + ' (' + env.version.revision + ')';
};
// TODO: docs
cli.printVersion = function(cb) {
console.log( cli.getVersion() );
if (cb) {
cb(0);
}
};
// TODO: docs
cli.main = function(cb) {
cli.scanFiles();
if (env.sourceFiles.length) {
cli.createParser()
.parseFiles()
.processParseResults();
}
else {
console.log('There are no input files to process.\n');
cli.printHelp(cb);
}
env.run.finish = new Date();
cb(0);
};
function readPackageJson(filepath) {
var fs = require('jsdoc/fs');
try {
return stripJsonComments( fs.readFileSync(filepath, 'utf8') );
}
catch (e) {
logger.error('Unable to read the package file "%s"', filepath);
return null;
}
}
function buildSourceList() {
var fs = require('jsdoc/fs');
var Readme = require('jsdoc/readme');
var packageJson;
var readmeHtml;
var sourceFile;
var sourceFiles = env.opts._ ? env.opts._.slice(0) : [];
if (env.conf.source && env.conf.source.include) {
sourceFiles = sourceFiles.concat(env.conf.source.include);
}
// load the user-specified package/README files, if any
if (env.opts.package) {
packageJson = readPackageJson(env.opts.package);
}
if (env.opts.readme) {
readmeHtml = new Readme(env.opts.readme).html;
}
// source files named `package.json` or `README.md` get special treatment, unless the user
// explicitly specified a package and/or README file
for (var i = 0, l = sourceFiles.length; i < l; i++) {
sourceFile = sourceFiles[i];
if ( !env.opts.package && /\bpackage\.json$/i.test(sourceFile) ) {
packageJson = readPackageJson(sourceFile);
sourceFiles.splice(i--, 1);
}
if ( !env.opts.readme && /(\bREADME|\.md)$/i.test(sourceFile) ) {
readmeHtml = new Readme(sourceFile).html;
sourceFiles.splice(i--, 1);
}
}
props.packageJson = packageJson;
env.opts.readme = readmeHtml;
return sourceFiles;
}
// TODO: docs
cli.scanFiles = function() {
var Filter = require('jsdoc/src/filter').Filter;
var filter;
env.opts._ = buildSourceList();
// are there any files to scan and parse?
if (env.conf.source && env.opts._.length) {
filter = new Filter(env.conf.source);
env.sourceFiles = app.jsdoc.scanner.scan(env.opts._, (env.opts.recurse ? 10 : undefined),
filter);
}
return cli;
};
function resolvePluginPaths(paths) {
var path = require('jsdoc/path');
var pluginPaths = [];
paths.forEach(function(plugin) {
var basename = path.basename(plugin);
var dirname = path.dirname(plugin);
var pluginPath = path.getResourcePath(dirname);
if (!pluginPath) {
logger.error('Unable to find the plugin "%s"', plugin);
return;
}
pluginPaths.push( path.join(pluginPath, basename) );
});
return pluginPaths;
}
cli.createParser = function() {
var handlers = require('jsdoc/src/handlers');
var parser = require('jsdoc/src/parser');
var path = require('jsdoc/path');
var plugins = require('jsdoc/plugins');
app.jsdoc.parser = parser.createParser(env.conf.parser);
if (env.conf.plugins) {
env.conf.plugins = resolvePluginPaths(env.conf.plugins);
plugins.installPlugins(env.conf.plugins, app.jsdoc.parser);
}
handlers.attachTo(app.jsdoc.parser);
return cli;
};
cli.parseFiles = function() {
var augment = require('jsdoc/augment');
var borrow = require('jsdoc/borrow');
var Package = require('jsdoc/package').Package;
var docs;
var packageDocs;
props.docs = docs = app.jsdoc.parser.parse(env.sourceFiles, env.opts.encoding);
// If there is no package.json, just create an empty package
packageDocs = new Package(props.packageJson);
packageDocs.files = env.sourceFiles || [];
docs.push(packageDocs);
logger.debug('Indexing doclets...');
borrow.indexAll(docs);
logger.debug('Adding inherited symbols, mixins, and interface implementations...');
augment.augmentAll(docs);
logger.debug('Adding borrowed doclets...');
borrow.resolveBorrows(docs);
logger.debug('Post-processing complete.');
app.jsdoc.parser.fireProcessingComplete(docs);
return cli;
};
cli.processParseResults = function() {
if (env.opts.explain) {
cli.dumpParseResults();
}
else {
cli.resolveTutorials();
cli.generateDocs();
}
return cli;
};
cli.dumpParseResults = function() {
global.dump(props.docs);
return cli;
};
cli.resolveTutorials = function() {
var resolver = require('jsdoc/tutorial/resolver');
if (env.opts.tutorials) {
resolver.load(env.opts.tutorials);
resolver.resolve();
}
return cli;
};
cli.generateDocs = function() {
var path = require('jsdoc/path');
var resolver = require('jsdoc/tutorial/resolver');
var taffy = require('taffydb').taffy;
var template;
env.opts.template = (function() {
var publish = env.opts.template || 'templates/default';
var templatePath = path.getResourcePath(publish);
// if we didn't find the template, keep the user-specified value so the error message is
// useful
return templatePath || env.opts.template;
})();
try {
template = require(env.opts.template + '/publish');
}
catch(e) {
logger.fatal('Unable to load template: ' + e.message || e);
}
// templates should include a publish.js file that exports a "publish" function
if (template.publish && typeof template.publish === 'function') {
logger.printInfo('Generating output files...');
template.publish(
taffy(props.docs),
env.opts,
resolver.root
);
logger.info('complete.');
}
else {
logger.fatal(env.opts.template + ' does not export a "publish" function. Global ' +
'"publish" functions are no longer supported.');
}
return cli;
};
// TODO: docs
cli.exit = function(exitCode, message) {
if (message && exitCode > 0) {
console.error(message);
}
process.exit(exitCode || 0);
};
return cli;
})();
+17
View File
@@ -0,0 +1,17 @@
{
"tags": {
"allowUnknownTags": true
},
"source": {
"includePattern": ".+\\.js(doc)?$",
"excludePattern": "(^|\\/|\\\\)_"
},
"plugins": [],
"templates": {
"cleverLinks": false,
"monospaceLinks": false,
"default": {
"outputSourceFiles": true
}
}
}
+184
View File
@@ -0,0 +1,184 @@
#!/usr/bin/env node
/*global arguments, require: true */
/**
* @project jsdoc
* @author Michael Mathews <micmath@gmail.com>
* @license See LICENSE.md file included in this distribution.
*/
/**
* Data representing the environment in which this app is running.
*
* @namespace
* @name env
*/
global.env = {
/**
* Running start and finish times.
*
* @memberof env
*/
run: {
start: new Date(),
finish: null
},
/**
* The command-line arguments passed into JSDoc.
*
* @type Array
* @memberof env
*/
args: [],
/**
* The parsed JSON data from the configuration file.
*
* @type Object
* @memberof env
*/
conf: {},
/**
* The absolute path to the base directory of the JSDoc application.
*
* @private
* @type string
* @memberof env
*/
dirname: '.',
/**
* The user's working directory at the time that JSDoc was started.
*
* @private
* @type string
* @memberof env
*/
pwd: null,
/**
* The command-line options, parsed into a key/value hash.
*
* @type Object
* @memberof env
* @example if (global.env.opts.help) { console.log('Helpful message.'); }
*/
opts: {},
/**
* The source files that JSDoc will parse.
* @type Array
* @memberof env
*/
sourceFiles: [],
/**
* The JSDoc version number and revision date.
*
* @type Object
* @memberof env
*/
version: {}
};
// initialize the environment for the current JavaScript VM
(function(args) {
'use strict';
var path;
if (args[0] && typeof args[0] === 'object') {
// we should be on Node.js
args = [__dirname, process.cwd()];
path = require('path');
// Create a custom require method that adds `lib/jsdoc` and `node_modules` to the module
// lookup path. This makes it possible to `require('jsdoc/foo')` from external templates and
// plugins, and within JSDoc itself. It also allows external templates and plugins to
// require JSDoc's module dependencies without installing them locally.
require = require('requizzle')({
requirePaths: {
before: [path.join(__dirname, 'lib')],
after: [path.join(__dirname, 'node_modules')]
},
infect: true
});
}
require('./lib/jsdoc/util/runtime').initialize(args);
})( Array.prototype.slice.call(arguments, 0) );
/**
* Data that must be shared across the entire application.
*
* @namespace
* @name app
*/
global.app = {
jsdoc: {
name: require('./lib/jsdoc/name'),
parser: null,
scanner: new (require('./lib/jsdoc/src/scanner').Scanner)()
}
};
/**
* Recursively print an object's properties to stdout. This method is safe to use with objects that
* contain circular references. In addition, on Mozilla Rhino, this method is safe to use with
* native Java objects.
*
* @global
* @name dump
* @private
* @param {Object} obj - Object(s) to print to stdout.
*/
global.dump = function() {
'use strict';
var _dump = require('./lib/jsdoc/util/dumper').dump;
for (var i = 0, l = arguments.length; i < l; i++) {
console.log( _dump(arguments[i]) );
}
};
(function() {
'use strict';
var logger = require('./lib/jsdoc/util/logger');
var runtime = require('./lib/jsdoc/util/runtime');
var cli = require('./cli');
function cb(errorCode) {
cli.logFinish();
cli.exit(errorCode || 0);
}
cli.setVersionInfo()
.loadConfig();
if (!global.env.opts.test) {
cli.configureLogger();
}
cli.logStart();
// On Rhino, we use a try/catch block so we can log the Java exception (if available)
if ( runtime.isRhino() ) {
try {
cli.runCommand(cb);
}
catch(e) {
if (e.rhinoException) {
logger.fatal( e.rhinoException.printStackTrace() );
} else {
console.trace(e);
cli.exit(1);
}
}
}
else {
cli.runCommand(cb);
}
})();
@@ -0,0 +1,492 @@
'use strict';
/**
* Provides methods for augmenting the parse results based on their content.
* @module jsdoc/augment
*/
var doop = require('jsdoc/util/doop');
var name = require('jsdoc/name');
var hasOwnProp = Object.prototype.hasOwnProperty;
function mapDependencies(index, propertyName) {
var dependencies = {};
var doc;
var doclets;
var kinds = ['class', 'external', 'mixin'];
var len = 0;
Object.keys(index).forEach(function(name) {
doclets = index[name];
for (var i = 0, ii = doclets.length; i < ii; i++) {
doc = doclets[i];
if (kinds.indexOf(doc.kind) !== -1) {
dependencies[name] = {};
if (hasOwnProp.call(doc, propertyName)) {
len = doc[propertyName].length;
for (var j = 0; j < len; j++) {
dependencies[name][doc[propertyName][j]] = true;
}
}
}
}
});
return dependencies;
}
function Sorter(dependencies) {
this.dependencies = dependencies;
this.visited = {};
this.sorted = [];
}
Sorter.prototype.visit = function(key) {
var self = this;
if (!(key in this.visited)) {
this.visited[key] = true;
if (this.dependencies[key]) {
Object.keys(this.dependencies[key]).forEach(function(path) {
self.visit(path);
});
}
this.sorted.push(key);
}
};
Sorter.prototype.sort = function() {
var self = this;
Object.keys(this.dependencies).forEach(function(key) {
self.visit(key);
});
return this.sorted;
};
function sort(dependencies) {
var sorter = new Sorter(dependencies);
return sorter.sort();
}
function getMembers(longname, docs, scopes) {
var candidate;
var members = [];
for (var i = 0, l = docs.length; i < l; i++) {
candidate = docs[i];
if (candidate.memberof === longname &&
(!scopes || !scopes.length || scopes.indexOf(candidate.scope) !== -1)) {
members.push(candidate);
}
}
return members;
}
function addDocletProperty(doclets, propName, value) {
for (var i = 0, l = doclets.length; i < l; i++) {
doclets[i][propName] = value;
}
}
function reparentDoclet(parent, child) {
var parts = name.shorten(child.longname);
parts.memberof = parent.longname;
child.memberof = parent.longname;
child.longname = name.combine(parts);
}
function parentIsClass(parent) {
return parent.kind === 'class';
}
function staticToInstance(doclet) {
var parts = name.shorten(doclet.longname);
parts.scope = name.SCOPE.PUNC.INSTANCE;
doclet.longname = name.combine(parts);
doclet.scope = name.SCOPE.NAMES.INSTANCE;
}
/**
* Update the list of doclets to be added to another symbol.
*
* We add only one doclet per longname. For example: If `ClassA` inherits from two classes that both
* use the same method name, `ClassA` gets docs for one method rather than two.
*
* Also, the last symbol wins for any given longname. For example: If you write `@extends Class1
* @extends Class2`, and both classes have an instance method called `myMethod`, you get the docs
* from `Class2#myMethod`.
*
* @private
* @param {Array.<module:jsdoc/doclet.Doclet>} additions - An array of doclets that will be added to
* another symbol.
* @param {Object.<string, number>} indexes - A dictionary of indexes into the `additions` array.
* Each key is a longname, and each value is the index of the longname's doclet.
* @return {void}
*/
function updateAddedDoclets(doclet, additions, indexes) {
if (typeof indexes[doclet.longname] !== 'undefined') {
// replace the existing doclet
additions[indexes[doclet.longname]] = doclet;
}
else {
// add the doclet to the array, and track its index
additions.push(doclet);
indexes[doclet.longname] = additions.length - 1;
}
}
function explicitlyInherits(doclets) {
var doclet;
var inherits = false;
for (var i = 0, l = doclets.length; i < l; i++) {
doclet = doclets[i];
if (typeof doclet.inheritdoc !== 'undefined' || typeof doclet.override !== 'undefined') {
inherits = true;
break;
}
}
return inherits;
}
function getInheritedAdditions(doclets, docs, documented) {
var additionIndexes;
var additions = [];
var doc;
var parents;
var members;
var member;
var parts;
// doclets will be undefined if the inherited symbol isn't documented
doclets = doclets || [];
for (var i = 0, ii = doclets.length; i < ii; i++) {
doc = doclets[i];
parents = doc.augments;
if (parents && doc.kind === 'class') {
// reset the lookup table of added doclet indexes by longname
additionIndexes = {};
for (var j = 0, jj = parents.length; j < jj; j++) {
members = getMembers(parents[j], docs, ['instance']);
for (var k = 0, kk = members.length; k < kk; k++) {
member = doop(members[k]);
if (!member.inherited) {
member.inherits = member.longname;
}
member.inherited = true;
// TODO: this will fail on longnames like: MyClass#"quoted#Longname"
// and nested instance members like: MyClass#MyOtherClass#myMethod;
// switch to updateLongname()!
member.memberof = doc.longname;
parts = member.longname.split('#');
parts[0] = doc.longname;
member.longname = parts.join('#');
// Indicate what the descendant is overriding. (We only care about the closest
// ancestor. For classes A > B > C, if B#a overrides A#a, and C#a inherits B#a,
// we don't want the doclet for C#a to say that it overrides A#a.)
addDocletProperty([member], 'overrides', members[k].longname);
// Add the ancestor's docs unless the descendant overrides the ancestor AND
// documents the override.
if ( !hasOwnProp.call(documented, member.longname) ) {
updateAddedDoclets(member, additions, additionIndexes);
}
// If the descendant used an @inheritdoc or @override tag, add the ancestor's
// docs, and ignore the existing doclets.
else if ( explicitlyInherits(documented[member.longname]) ) {
// Ignore any existing doclets. (This is safe because we only get here if
// `member.longname` is an own property of `documented`.)
addDocletProperty(documented[member.longname], 'ignore', true);
updateAddedDoclets(member, additions, additionIndexes);
// Remove property that's no longer accurate.
if (member.virtual) {
delete member.virtual;
}
// Remove properties that we no longer need.
if (member.inheritdoc) {
delete member.inheritdoc;
}
if (member.override) {
delete member.override;
}
}
// If the descendant overrides the ancestor and documents the override,
// update the doclets to indicate what the descendant is overriding.
else {
addDocletProperty(documented[member.longname], 'overrides',
members[k].longname);
}
}
}
}
}
return additions;
}
function updateMixes(mixedDoclet, mixedLongname) {
var idx;
var mixedName;
var names;
// take the fast path if there's no array of mixed-in longnames
if (!mixedDoclet.mixes) {
mixedDoclet.mixes = [mixedLongname];
}
else {
// find the short name of the longname we're mixing in
mixedName = name.shorten(mixedLongname).name;
// find the short name of each previously mixed-in symbol
names = mixedDoclet.mixes.map(function(m) {
return name.shorten(mixedDoclet.longname).name;
});
// if we're mixing `myMethod` into `MixinC` from `MixinB`, and `MixinB` had the method mixed
// in from `MixinA`, don't show `MixinA.myMethod` in the `mixes` list
idx = names.indexOf(mixedName);
if (idx !== -1) {
mixedDoclet.mixes.splice(idx, 1);
}
mixedDoclet.mixes.push(mixedLongname);
}
}
// TODO: try to reduce overlap with getInheritedAdditions
function getMixedInAdditions(mixinDoclets, allDoclets, commentedDoclets) {
var additionIndexes;
var additions = [];
var doclet;
var idx;
var mixedDoclet;
var mixedDoclets;
var mixes;
// mixinDoclets will be undefined if the mixed-in symbol isn't documented
mixinDoclets = mixinDoclets || [];
for (var i = 0, ii = mixinDoclets.length; i < ii; i++) {
doclet = mixinDoclets[i];
mixes = doclet.mixes;
if (mixes) {
// reset the lookup table of added doclet indexes by longname
additionIndexes = {};
for (var j = 0, jj = mixes.length; j < jj; j++) {
mixedDoclets = getMembers(mixes[j], allDoclets, ['static']);
for (var k = 0, kk = mixedDoclets.length; k < kk; k++) {
mixedDoclet = doop(mixedDoclets[k]);
updateMixes(mixedDoclet, mixedDoclet.longname);
mixedDoclet.mixed = true;
reparentDoclet(doclet, mixedDoclet);
// if we're mixing into a class, treat the mixed-in symbol as an instance member
if (parentIsClass(doclet)) {
staticToInstance(mixedDoclet);
}
updateAddedDoclets(mixedDoclet, additions, additionIndexes);
}
}
}
}
return additions;
}
function augment(doclets, propertyName, docletFinder) {
var index = doclets.index.longname;
var dependencies = sort( mapDependencies(index, propertyName) );
dependencies.forEach(function(name) {
var additions = docletFinder.call(null, index[name], doclets, doclets.index.documented);
additions.forEach(function(addition) {
var longname = addition.longname;
if ( !hasOwnProp.call(index, longname) ) {
index[longname] = [];
}
index[longname].push(addition);
doclets.push(addition);
});
});
}
/**
* Add doclets to reflect class inheritance.
*
* For example, if `ClassA` has the instance method `myMethod`, and `ClassB` inherits from `ClassA`,
* calling this method creates a new doclet for `ClassB#myMethod`.
*
* @param {!Array.<module:jsdoc/doclet.Doclet>} doclets - The doclets generated by JSDoc.
* @param {!Object} doclets.index - The doclet index added by {@link module:jsdoc/borrow.indexAll}.
* @return {void}
*/
exports.addInherited = function(doclets) {
augment(doclets, 'augments', getInheritedAdditions);
};
/**
* Add doclets to reflect mixins. When a symbol is mixed into a class, the class' version of the
* mixed-in symbol is treated as an instance member.
*
* For example:
*
* + If `MixinA` has the static method `myMethod`, and `MixinB` mixes `MixinA`, calling this method
* creates a new doclet for the static method `MixinB.myMethod`.
* + If `MixinA` has the static method `myMethod`, and `ClassA` mixes `MixinA`, calling this method
* creates a new doclet for the instance method `ClassA#myMethod`.
*
* @param {!Array.<module:jsdoc/doclet.Doclet>} doclets - The doclets generated by JSDoc.
* @param {!Object} doclets.index - The doclet index added by {@link module:jsdoc/borrow.indexAll}.
* @return {void}
*/
exports.addMixedIn = function(doclets) {
augment(doclets, 'mixes', getMixedInAdditions);
};
// TODO: move as much of this as possible to jsdoc/borrow.indexAll
/**
* Update doclets to reflect implementations of interfaces.
*
* For example, if `InterfaceA` has the instance method `myMethod`, and `ClassA` implements
* `InterfaceA`, calling this method does the following:
*
* + Updates `InterfaceA` to indicate that it is implemented by `ClassA`
* + Updates `InterfaceA#myMethod` to indicate that it is implemented by `ClassA#myMethod`
* + Updates `ClassA#myMethod` to indicate that it implements `InterfaceA#myMethod`
*
* @param {!Array.<module:jsdoc/doclet.Doclet>} docs - The doclets generated by JSDoc.
* @param {!Object} doclets.index - The doclet index added by {@link module:jsdoc/borrow.indexAll}.
* @return {void}
*/
exports.addImplemented = function(docs) {
var docMap = {};
var interfaces = {};
var implemented = {};
var memberInfo = {};
docs.forEach(function(doc) {
var memberof = doc.memberof || doc.name;
if (!hasOwnProp.call(docMap, memberof)) {
docMap[memberof] = [];
}
docMap[memberof].push(doc);
if (doc.kind === 'interface') {
interfaces[doc.longname] = doc;
}
else if (doc.implements && doc.implements.length) {
if (!hasOwnProp.call(implemented, doc.memberof)) {
implemented[memberof] = [];
}
implemented[memberof].push(doc);
}
});
// create an dictionary of interface doclets
Object.keys(interfaces).forEach(function(ifaceName) {
var iface = interfaces[ifaceName];
if (hasOwnProp.call(docMap, iface.name)) {
docMap[iface.name].forEach(function(doc) {
var members = memberInfo[doc.memberof];
if (!members) {
members = memberInfo[doc.memberof] = {};
}
members[doc.name] = doc;
});
}
});
Object.keys(implemented).forEach(function(key) {
// implemented classes namespace.
var owner = implemented[key];
owner.forEach(function(klass) {
// class's interfaces
klass.implements.forEach(function(impl) {
var interfaceMember;
var interfaceMembers = memberInfo[impl];
var member;
var members;
// mark the interface as being implemented by the class
if (hasOwnProp.call(interfaces, impl)) {
interfaces[impl].implementations = interfaces[impl].implementations || [];
interfaces[impl].implementations.push(klass.longname);
}
// if the interface has no members, skip to the next owner
if (!interfaceMembers) {
return;
}
if (!hasOwnProp.call(docMap, klass.longname)) {
docMap[klass.longname] = [];
}
members = docMap[klass.longname];
for (var i = 0, len = members.length; i < len; i++) {
member = members[i];
interfaceMember = interfaceMembers && interfaceMembers[member.name];
// if we didn't find the member name in the interface, skip to the next member
if (!interfaceMember) {
continue;
}
// mark members that implement an interface
member.implements = member.implements || [];
member.implements.push(interfaceMember.longname);
// mark interface members that the symbol implements
interfaceMember.implementations = interfaceMember.implementations || [];
interfaceMember.implementations.push(member.longname);
}
});
});
});
};
/**
* Add and update doclets to reflect all of the following:
*
* + Inherited classes
* + Mixins
* + Interface implementations
*
* Calling this method is equivalent to calling all other methods exported by this module.
*
* @return {void}
*/
exports.augmentAll = function(doclets) {
exports.addInherited(doclets);
exports.addMixedIn(doclets);
exports.addImplemented(doclets);
};
+107
View File
@@ -0,0 +1,107 @@
/**
A collection of functions relating to resolving @borrows tags in JSDoc symbols.
@module jsdoc/borrow
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var doop = require('jsdoc/util/doop');
var logger = require('jsdoc/util/logger');
var SCOPE = require('jsdoc/name').SCOPE;
var hasOwnProp = Object.prototype.hasOwnProperty;
// TODO: add the index at parse time, so we don't have to iterate over all the doclets again
exports.indexAll = function(doclets) {
var borrowed = [];
var doclet;
var documented = {};
var longname = {};
for (var i = 0, l = doclets.length; i < l; i++) {
doclet = doclets[i];
// track all doclets by longname
if ( !hasOwnProp.call(longname, doclet.longname) ) {
longname[doclet.longname] = [];
}
longname[doclet.longname].push(doclet);
// track longnames of documented symbols
if (!doclet.undocumented) {
if ( !hasOwnProp.call(documented, doclet.longname) ) {
documented[doclet.longname] = [];
}
documented[doclet.longname].push(doclet);
}
// track doclets with a `borrowed` property
if ( hasOwnProp.call(doclet, 'borrowed') ) {
borrowed.push(doclet);
}
}
doclets.index = {
borrowed: borrowed,
documented: documented,
longname: longname
};
};
function cloneBorrowedDoclets(doclet, doclets) {
doclet.borrowed.forEach(function(borrowed) {
var borrowedDoclets = doclets.index.longname[borrowed.from];
var borrowedAs = borrowed.as || borrowed.from;
var clonedDoclets;
var parts;
var scopePunc;
if (borrowedDoclets) {
borrowedAs = borrowedAs.replace(/^prototype\./, SCOPE.PUNC.INSTANCE);
clonedDoclets = doop(borrowedDoclets).forEach(function(clone) {
// TODO: this will fail on longnames like '"Foo#bar".baz'
parts = borrowedAs.split(SCOPE.PUNC.INSTANCE);
if (parts.length === 2) {
clone.scope = SCOPE.NAMES.INSTANCE;
scopePunc = SCOPE.PUNC.INSTANCE;
}
else {
clone.scope = SCOPE.NAMES.STATIC;
scopePunc = SCOPE.PUNC.STATIC;
}
clone.name = parts.pop();
clone.memberof = doclet.longname;
clone.longname = clone.memberof + scopePunc + clone.name;
doclets.push(clone);
});
}
});
}
// requires docs to have been indexed: docs.index must be defined here
/**
Take a copy of the docs for borrowed symbols and attach them to the
docs for the borrowing symbol. This process changes the symbols involved,
moving docs from the "borrowed" array and into the general docs, then
deleting the "borrowed" array.
*/
exports.resolveBorrows = function(doclets) {
var doclet;
if (!doclets.index) {
logger.error('Unable to resolve borrowed symbols, because the docs have not been indexed.');
return;
}
for (var i = 0, l = doclets.index.borrowed.length; i < l; i++) {
doclet = doclets.index.borrowed[i];
cloneBorrowedDoclets(doclet, doclets);
delete doclet.borrowed;
}
doclets.index.borrowed = [];
};
@@ -0,0 +1,60 @@
/**
@overview
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
/**
@module jsdoc/config
*/
'use strict';
function mergeRecurse(target, source) {
Object.keys(source).forEach(function(p) {
if ( source[p].constructor === Object ) {
if ( !target[p] ) { target[p] = {}; }
mergeRecurse(target[p], source[p]);
}
else {
target[p] = source[p];
}
});
return target;
}
// required config values, override these defaults in your config.json if necessary
var defaults = {
tags: {
allowUnknownTags: true,
dictionaries: ['jsdoc', 'closure']
},
templates: {
monospaceLinks: false,
cleverLinks: false
},
source: {
includePattern: '.+\\.js(doc)?$',
excludePattern: ''
},
plugins: []
};
/**
@class
@classdesc Represents a JSDoc application configuration.
@param {string} [json] - The contents of config.json.
*/
function Config(json) {
json = JSON.parse( (json || '{}') );
this._config = mergeRecurse(defaults, json);
}
module.exports = Config;
/**
Get the merged configuration values.
*/
Config.prototype.get = function() {
return this._config;
};
+416
View File
@@ -0,0 +1,416 @@
/**
* @overview
* @author Michael Mathews <micmath@gmail.com>
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
/**
* @module jsdoc/doclet
*/
'use strict';
var _ = require('underscore');
var jsdoc = {
name: require('jsdoc/name'),
src: {
astnode: require('jsdoc/src/astnode'),
Syntax: require('jsdoc/src/syntax').Syntax
},
tag: {
Tag: require('jsdoc/tag').Tag,
dictionary: require('jsdoc/tag/dictionary')
}
};
var path = require('jsdoc/path');
var Syntax = jsdoc.src.Syntax;
var util = require('util');
function applyTag(doclet, tag) {
if (tag.title === 'name') {
doclet.name = tag.value;
}
if (tag.title === 'kind') {
doclet.kind = tag.value;
}
if (tag.title === 'description') {
doclet.description = tag.value;
}
}
// use the meta info about the source code to guess what the doclet kind should be
function codeToKind(code) {
var parent;
var isFunction = jsdoc.src.astnode.isFunction;
// default
var kind = 'member';
if (code.type === Syntax.FunctionDeclaration || code.type === Syntax.FunctionExpression) {
kind = 'function';
}
else if (code.node && code.node.parent) {
parent = code.node.parent;
if ( isFunction(parent) ) {
kind = 'param';
}
}
return kind;
}
function unwrap(docletSrc) {
if (!docletSrc) { return ''; }
// note: keep trailing whitespace for @examples
// extra opening/closing stars are ignored
// left margin is considered a star and a space
// use the /m flag on regex to avoid having to guess what this platform's newline is
docletSrc =
docletSrc.replace(/^\/\*\*+/, '') // remove opening slash+stars
.replace(/\**\*\/$/, '\\Z') // replace closing star slash with end-marker
.replace(/^\s*(\* ?|\\Z)/gm, '') // remove left margin like: spaces+star or spaces+end-marker
.replace(/\s*\\Z$/g, ''); // remove end-marker
return docletSrc;
}
/**
* Convert the raw source of the doclet comment into an array of pseudo-Tag objects.
* @private
*/
function toTags(docletSrc) {
var parsedTag;
var tagData = [];
var tagText;
var tagTitle;
// split out the basic tags, keep surrounding whitespace
// like: @tagTitle tagBody
docletSrc
// replace splitter ats with an arbitrary sequence
.replace(/^(\s*)@(\S)/gm, '$1\\@$2')
// then split on that arbitrary sequence
.split('\\@')
.forEach(function($) {
if ($) {
parsedTag = $.match(/^(\S+)(?:\s+(\S[\s\S]*))?/);
if (parsedTag) {
tagTitle = parsedTag[1];
tagText = parsedTag[2];
if (tagTitle) {
tagData.push({
title: tagTitle,
text: tagText
});
}
}
}
});
return tagData;
}
function fixDescription(docletSrc) {
if (!/^\s*@/.test(docletSrc) && docletSrc.replace(/\s/g, '').length) {
docletSrc = '@description ' + docletSrc;
}
return docletSrc;
}
/**
* Replace the existing tag dictionary with a new tag dictionary.
*
* Used for testing only.
*
* @private
* @param {module:jsdoc/tag/dictionary.Dictionary} dict - The new tag dictionary.
*/
exports._replaceDictionary = function _replaceDictionary(dict) {
jsdoc.tag.dictionary = dict;
require('jsdoc/tag')._replaceDictionary(dict);
require('jsdoc/util/templateHelper')._replaceDictionary(dict);
};
/**
* @class
* @classdesc Represents a single JSDoc comment.
* @param {string} docletSrc - The raw source code of the jsdoc comment.
* @param {object=} meta - Properties describing the code related to this comment.
*/
var Doclet = exports.Doclet = function(docletSrc, meta) {
var newTags = [];
/** The original text of the comment from the source code. */
this.comment = docletSrc;
this.setMeta(meta);
docletSrc = unwrap(docletSrc);
docletSrc = fixDescription(docletSrc);
newTags = toTags.call(this, docletSrc);
for (var i = 0, l = newTags.length; i < l; i++) {
this.addTag(newTags[i].title, newTags[i].text);
}
this.postProcess();
};
/** Called once after all tags have been added. */
Doclet.prototype.postProcess = function() {
var i;
var l;
if (!this.preserveName) {
jsdoc.name.resolve(this);
}
if (this.name && !this.longname) {
this.setLongname(this.name);
}
if (this.memberof === '') {
delete this.memberof;
}
if (!this.kind && this.meta && this.meta.code) {
this.addTag( 'kind', codeToKind(this.meta.code) );
}
if (this.variation && this.longname && !/\)$/.test(this.longname) ) {
this.longname += '(' + this.variation + ')';
}
// add in any missing param names
if (this.params && this.meta && this.meta.code && this.meta.code.paramnames) {
for (i = 0, l = this.params.length; i < l; i++) {
if (!this.params[i].name) {
this.params[i].name = this.meta.code.paramnames[i] || '';
}
}
}
};
/**
* Add a tag to the doclet.
*
* @param {string} title - The title of the tag being added.
* @param {string} [text] - The text of the tag being added.
*/
Doclet.prototype.addTag = function(title, text) {
var tagDef = jsdoc.tag.dictionary.lookUp(title),
newTag = new jsdoc.tag.Tag(title, text, this.meta);
if (tagDef && tagDef.onTagged) {
tagDef.onTagged(this, newTag);
}
if (!tagDef) {
this.tags = this.tags || [];
this.tags.push(newTag);
}
applyTag(this, newTag);
};
function removeGlobal(longname) {
var globalRegexp = new RegExp('^' + jsdoc.name.LONGNAMES.GLOBAL + '\\.?');
return longname.replace(globalRegexp, '');
}
/**
* Set the doclet's `memberof` property.
*
* @param {string} sid - The longname of the doclet's parent symbol.
*/
Doclet.prototype.setMemberof = function(sid) {
/**
* The longname of the symbol that contains this one, if any.
* @type string
*/
this.memberof = removeGlobal(sid)
.replace(/\.prototype/g, jsdoc.name.SCOPE.PUNC.INSTANCE);
};
/**
* Set the doclet's `longname` property.
*
* @param {string} name - The longname for the doclet.
*/
Doclet.prototype.setLongname = function(name) {
/**
* The fully resolved symbol name.
* @type string
*/
this.longname = removeGlobal(name);
if (jsdoc.tag.dictionary.isNamespace(this.kind)) {
this.longname = jsdoc.name.applyNamespace(this.longname, this.kind);
}
};
/**
* Get the full path to the source file that is associated with a doclet.
*
* @private
* @param {module:jsdoc/doclet.Doclet} The doclet to check for a filepath.
* @return {string} The path to the doclet's source file, or an empty string if the path is not
* available.
*/
function getFilepath(doclet) {
if (!doclet || !doclet.meta || !doclet.meta.filename) {
return '';
}
return path.join(doclet.meta.path || '', doclet.meta.filename);
}
/**
* Set the doclet's `scope` property. Must correspond to a scope name that is defined in
* {@link module:jsdoc/name.SCOPE.NAMES}.
*
* @param {module:jsdoc/name.SCOPE.NAMES} scope - The scope for the doclet relative to the symbol's
* parent.
* @throws {Error} If the scope name is not recognized.
*/
Doclet.prototype.setScope = function(scope) {
var errorMessage;
var filepath;
var scopeNames = _.values(jsdoc.name.SCOPE.NAMES);
if (scopeNames.indexOf(scope) === -1) {
filepath = getFilepath(this);
errorMessage = util.format('The scope name "%s" is not recognized. Use one of the ' +
'following values: %j', scope, scopeNames);
if (filepath) {
errorMessage += util.format(' (Source file: %s)', filepath);
}
throw new Error(errorMessage);
}
this.scope = scope;
};
/**
* Add a symbol to this doclet's `borrowed` array.
*
* @param {string} source - The longname of the symbol that is the source.
* @param {string} target - The name the symbol is being assigned to.
*/
Doclet.prototype.borrow = function(source, target) {
var about = { from: source };
if (target) {
about.as = target;
}
if (!this.borrowed) {
/**
* A list of symbols that are borrowed by this one, if any.
* @type Array.<string>
*/
this.borrowed = [];
}
this.borrowed.push(about);
};
Doclet.prototype.mix = function(source) {
/**
* A list of symbols that are mixed into this one, if any.
* @type Array.<string>
*/
this.mixes = this.mixes || [];
this.mixes.push(source);
};
/**
* Add a symbol to the doclet's `augments` array.
*
* @param {string} base - The longname of the base symbol.
*/
Doclet.prototype.augment = function(base) {
/**
* A list of symbols that are augmented by this one, if any.
* @type Array.<string>
*/
this.augments = this.augments || [];
this.augments.push(base);
};
/**
* Set the `meta` property of this doclet.
*
* @param {object} meta
*/
Doclet.prototype.setMeta = function(meta) {
/**
* Information about the source code associated with this doclet.
* @namespace
*/
this.meta = this.meta || {};
if (meta.range) {
/**
* The positions of the first and last characters of the code associated with this doclet.
* @type Array.<number>
*/
this.meta.range = meta.range.slice(0);
}
if (meta.lineno) {
/**
* The name of the file containing the code associated with this doclet.
* @type string
*/
this.meta.filename = path.basename(meta.filename);
/**
* The line number of the code associated with this doclet.
* @type number
*/
this.meta.lineno = meta.lineno;
var pathname = path.dirname(meta.filename);
if (pathname && pathname !== '.') {
this.meta.path = pathname;
}
}
/**
* Information about the code symbol.
* @namespace
*/
this.meta.code = this.meta.code || {};
if (meta.id) { this.meta.code.id = meta.id; }
if (meta.code) {
if (meta.code.name) {
/** The name of the symbol in the source code. */
this.meta.code.name = meta.code.name;
}
if (meta.code.type) {
/** The type of the symbol in the source code. */
this.meta.code.type = meta.code.type;
}
// the AST node is only enumerable in debug mode, which reduces clutter for the
// --explain/-X option
if (meta.code.node) {
Object.defineProperty(this.meta.code, 'node', {
value: meta.code.node,
enumerable: global.env.opts.debug ? true : false
});
}
if (meta.code.funcscope) {
this.meta.code.funcscope = meta.code.funcscope;
}
if (meta.code.value) {
/** The value of the symbol in the source code. */
this.meta.code.value = meta.code.value;
}
if (meta.code.paramnames) {
this.meta.code.paramnames = meta.code.paramnames.slice(0);
}
}
};
+74
View File
@@ -0,0 +1,74 @@
/**
* Extended version of the standard `fs` module.
* @module jsdoc/fs
*/
'use strict';
var fs = require('fs');
var path = require('path');
var runtime = require('jsdoc/util/runtime');
var ls = exports.ls = function(dir, recurse, _allFiles, _path) {
var file;
var files;
var isFile;
// first pass
if (_path === undefined) {
_allFiles = [];
_path = [dir];
}
if (!_path.length) {
return _allFiles;
}
if (recurse === undefined) {
recurse = 1;
}
try {
isFile = fs.statSync(dir).isFile();
}
catch (e) {
isFile = false;
}
if (isFile) {
files = [dir];
}
else {
files = fs.readdirSync(dir);
}
for (var i = 0, l = files.length; i < l; i++) {
file = String(files[i]);
// skip dot files
if (file.match(/^\.[^\.\/\\]/)) {
continue;
}
if ( fs.statSync(path.join(_path.join('/'), file)).isDirectory() ) {
// it's a directory
_path.push(file);
if (_path.length - 1 < recurse) {
ls(_path.join('/'), recurse, _allFiles, _path);
}
_path.pop();
}
else {
// it's a file
_allFiles.push( path.normalize(path.join(_path.join('/'), file)) );
}
}
return _allFiles;
};
// export the VM-specific implementations of the extra methods
// TODO: document extra methods here
var extras = require( runtime.getModulePath('fs') );
Object.keys(extras).forEach(function(extra) {
exports[extra] = extras[extra];
});
+432
View File
@@ -0,0 +1,432 @@
/**
A collection of functions relating to JSDoc symbol name manipulation.
@module jsdoc/name
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var _ = require('underscore');
var escape = require('escape-string-regexp');
var hasOwnProp = Object.prototype.hasOwnProperty;
/**
* Longnames that have a special meaning in JSDoc.
*
* @enum {string}
* @static
* @memberof module:jsdoc/name
*/
var LONGNAMES = exports.LONGNAMES = {
/** Longname used for doclets that do not have a longname, such as anonymous functions. */
ANONYMOUS: '<anonymous>',
/** Longname that represents global scope. */
GLOBAL: '<global>'
};
// Module namespace prefix.
var MODULE_NAMESPACE = 'module:';
/**
* Names and punctuation marks that identify doclet scopes.
*
* @enum {string}
* @static
* @memberof module:jsdoc/name
*/
var SCOPE = exports.SCOPE = {
NAMES: {
GLOBAL: 'global',
INNER: 'inner',
INSTANCE: 'instance',
STATIC: 'static'
},
PUNC: {
INNER: '~',
INSTANCE: '#',
STATIC: '.'
}
};
// For backwards compatibility, this enum must use lower-case keys
var scopeToPunc = exports.scopeToPunc = {
'inner': SCOPE.PUNC.INNER,
'instance': SCOPE.PUNC.INSTANCE,
'static': SCOPE.PUNC.STATIC
};
var puncToScope = exports.puncToScope = _.invert(scopeToPunc);
var DEFAULT_SCOPE = SCOPE.NAMES.STATIC;
var SCOPE_PUNC = _.values(SCOPE.PUNC);
var SCOPE_PUNC_STRING = '[' + SCOPE_PUNC.join() + ']';
var REGEXP_LEADING_SCOPE = new RegExp('^(' + SCOPE_PUNC_STRING + ')');
var REGEXP_TRAILING_SCOPE = new RegExp('(' + SCOPE_PUNC_STRING + ')$');
var DESCRIPTION = '(?:(?:[ \\t]*\\-\\s*|\\s+)(\\S[\\s\\S]*))?$';
var REGEXP_DESCRIPTION = new RegExp(DESCRIPTION);
var REGEXP_NAME_DESCRIPTION = new RegExp('^(\\[[^\\]]+\\]|\\S+)' + DESCRIPTION);
function nameIsLongname(name, memberof) {
var regexp = new RegExp('^' + escape(memberof) + SCOPE_PUNC_STRING);
return regexp.test(name);
}
function prototypeToPunc(name) {
return name.replace(/(?:^|\.)prototype\.?/g, SCOPE.PUNC.INSTANCE);
}
// TODO: deprecate exports.resolve in favor of a better name
/**
Resolves the longname, memberof, variation and name values of the given doclet.
@param {module:jsdoc/doclet.Doclet} doclet
*/
exports.resolve = function(doclet) {
var about = {};
var memberof = doclet.memberof || '';
var name = doclet.name ? String(doclet.name) : '';
var parentDoc;
// change MyClass.prototype.instanceMethod to MyClass#instanceMethod
// (but not in function params, which lack doclet.kind)
// TODO: check for specific doclet.kind values (probably function, class, and module)
if (name && doclet.kind) {
name = prototypeToPunc(name);
}
doclet.name = name;
// member of a var in an outer scope?
if (name && !memberof && doclet.meta.code && doclet.meta.code.funcscope) {
name = doclet.longname = doclet.meta.code.funcscope + SCOPE.PUNC.INNER + name;
}
if (memberof || doclet.forceMemberof) { // @memberof tag given
memberof = prototypeToPunc(memberof);
// the name is a complete longname, like @name foo.bar, @memberof foo
if (name && nameIsLongname(name, memberof) && name !== memberof) {
about = exports.shorten(name, (doclet.forceMemberof ? memberof : undefined));
}
// the name and memberof are identical and refer to a module,
// like @name module:foo, @memberof module:foo (probably a member like 'var exports')
else if (name && name === memberof && name.indexOf(MODULE_NAMESPACE) === 0) {
about = exports.shorten(name, (doclet.forceMemberof ? memberof : undefined));
}
// the name and memberof are identical, like @name foo, @memberof foo
else if (name && name === memberof) {
doclet.scope = doclet.scope || DEFAULT_SCOPE;
name = memberof + scopeToPunc[doclet.scope] + name;
about = exports.shorten(name, (doclet.forceMemberof ? memberof : undefined));
}
// like @memberof foo# or @memberof foo~
else if (name && REGEXP_TRAILING_SCOPE.test(memberof) ) {
about = exports.shorten(memberof + name, (doclet.forceMemberof ? memberof : undefined));
}
else if (name && doclet.scope) {
about = exports.shorten(memberof + (scopeToPunc[doclet.scope] || '') + name,
(doclet.forceMemberof ? memberof : undefined));
}
}
else { // no @memberof
about = exports.shorten(name);
}
if (about.name) {
doclet.name = about.name;
}
if (about.memberof) {
doclet.setMemberof(about.memberof);
}
if (about.longname && (!doclet.longname || doclet.longname === doclet.name)) {
doclet.setLongname(about.longname);
}
if (doclet.scope === SCOPE.NAMES.GLOBAL) { // via @global tag?
doclet.setLongname(doclet.name);
delete doclet.memberof;
}
else if (about.scope) {
if (about.memberof === LONGNAMES.GLOBAL) { // via @memberof <global> ?
doclet.scope = SCOPE.NAMES.GLOBAL;
}
else {
doclet.scope = puncToScope[about.scope];
}
}
else if (doclet.name && doclet.memberof && !doclet.longname) {
if ( REGEXP_LEADING_SCOPE.test(doclet.name) ) {
doclet.scope = puncToScope[RegExp.$1];
doclet.name = doclet.name.substr(1);
}
else {
doclet.scope = DEFAULT_SCOPE;
}
doclet.setLongname(doclet.memberof + scopeToPunc[doclet.scope] + doclet.name);
}
if (about.variation) {
doclet.variation = about.variation;
}
// if we never found a longname, just use an empty string
if (!doclet.longname) {
doclet.longname = '';
}
};
/**
@method module:jsdoc/name.applyNamespace
@param {string} longname The full longname of the symbol.
@param {string} ns The namespace to be applied.
@returns {string} The longname with the namespace applied.
*/
exports.applyNamespace = function(longname, ns) {
var nameParts = exports.shorten(longname),
name = nameParts.name;
longname = nameParts.longname;
if ( !/^[a-zA-Z]+?:.+$/i.test(name) ) {
longname = longname.replace( new RegExp(escape(name) + '$'), ns + ':' + name );
}
return longname;
};
// TODO: docs
function atomize(longname, sliceChars, forcedMemberof) {
var i;
var memberof = '';
var name = '';
var parts;
var partsRegExp;
var scopePunc = '';
var token;
var tokens = [];
var variation;
// quoted strings in a longname are atomic, so we convert them to tokens
longname = longname.replace(/(\[?["'].+?["']\]?)/g, function($) {
var dot = '';
if ( /^\[/.test($) ) {
dot = '.';
$ = $.replace( /^\[/g, '' ).replace( /\]$/g, '' );
}
token = '@{' + tokens.length + '}@';
tokens.push($);
return dot + token; // foo["bar"] => foo.@{1}@
});
longname = prototypeToPunc(longname);
if (forcedMemberof !== undefined) {
partsRegExp = new RegExp('^(.*?)([' + sliceChars.join() + ']?)$');
name = longname.substr(forcedMemberof.length);
parts = forcedMemberof.match(partsRegExp);
if (parts[1]) {
memberof = parts[1] || forcedMemberof;
}
if (parts[2]) {
scopePunc = parts[2];
}
}
else if (longname) {
parts = (longname.match(new RegExp('^(:?(.+)([' + sliceChars.join() + ']))?(.+?)$')) || [])
.reverse();
name = parts[0] || '';
scopePunc = parts[1] || '';
memberof = parts[2] || '';
}
// like /** @name foo.bar(2) */
if ( /(.+)\(([^)]+)\)$/.test(name) ) {
name = RegExp.$1;
variation = RegExp.$2;
}
// restore quoted strings
i = tokens.length;
while (i--) {
longname = longname.replace('@{' + i + '}@', tokens[i]);
memberof = memberof.replace('@{' + i + '}@', tokens[i]);
scopePunc = scopePunc.replace('@{' + i + '}@', tokens[i]);
name = name.replace('@{' + i + '}@', tokens[i]);
}
return {
longname: longname,
memberof: memberof,
scope: scopePunc,
name: name,
variation: variation
};
}
// TODO: deprecate exports.shorten in favor of a better name
/**
Given a longname like "a.b#c(2)", slice it up into an object
containing the memberof, the scope, the name, and variation.
@param {string} longname
@param {string} forcedMemberof
@returns {object} Representing the properties of the given name.
*/
exports.shorten = function(longname, forcedMemberof) {
return atomize(longname, SCOPE_PUNC, forcedMemberof);
};
// TODO: docs
exports.combine = function(parts) {
return '' +
(parts.memberof || '') +
(parts.scope || '') +
(parts.name || '') +
(parts.variation || '');
};
function stripVariation(name) {
return name.replace(/\([^)]\)$/, '');
}
function splitLongname(longname, options) {
var chunks = [];
var currentNameInfo;
var nameInfo = {};
var previousName = longname;
var splitters = SCOPE_PUNC.concat('/');
options = _.defaults(options || {}, {
includeVariation: true
});
do {
if (!options.includeVariation) {
previousName = stripVariation(previousName);
}
currentNameInfo = nameInfo[previousName] = atomize(previousName, splitters);
previousName = currentNameInfo.memberof;
chunks.push(currentNameInfo.scope + currentNameInfo.name);
} while (previousName);
return {
chunks: chunks.reverse(),
nameInfo: nameInfo
};
}
// TODO: docs
exports.longnamesToTree = function longnamesToTree(longnames, doclets) {
var splitOptions = { includeVariation: false };
var tree = {};
longnames.forEach(function(longname) {
var chunk;
var currentLongname = '';
var currentNavItem = tree;
var nameInfo;
var processed;
// don't try to add empty longnames to the tree
if (!longname) {
return;
}
processed = splitLongname(longname, splitOptions);
nameInfo = processed.nameInfo;
processed.chunks.forEach(function(chunk) {
currentLongname += chunk;
if (!hasOwnProp.call(currentNavItem, chunk)) {
currentNavItem[chunk] = nameInfo[currentLongname];
}
if (currentNavItem[chunk]) {
currentNavItem[chunk].doclet = doclets ? doclets[currentLongname] : null;
currentNavItem[chunk].children = currentNavItem[chunk].children || {};
currentNavItem = currentNavItem[chunk].children;
}
});
});
return tree;
};
/**
Split a string that starts with a name and ends with a description into its parts.
Allows the defaultvalue (if present) to contain brackets. If the name is found to have
mismatched brackets, null is returned.
@param {string} nameDesc
@returns {object} Hash with "name" and "description" properties.
*/
function splitNameMatchingBrackets(nameDesc) {
var buffer = [];
var c;
var stack = 0;
var stringEnd = null;
for (var i = 0; i < nameDesc.length; ++i) {
c = nameDesc[i];
buffer.push(c);
if (stringEnd) {
if (c === '\\' && i + 1 < nameDesc.length) {
buffer.push(nameDesc[++i]);
} else if (c === stringEnd) {
stringEnd = null;
}
} else if (c === '"' || c === "'") {
stringEnd = c;
} else if (c === '[') {
++stack;
} else if (c === ']') {
if (--stack === 0) {
break;
}
}
}
if (stack || stringEnd) {
return null;
}
nameDesc.substr(i).match(REGEXP_DESCRIPTION);
return {
name: buffer.join(''),
description: RegExp.$1
};
}
// TODO: deprecate exports.splitName in favor of a better name
/**
Split a string that starts with a name and ends with a description into its parts.
@param {string} nameDesc
@returns {object} Hash with "name" and "description" properties.
*/
exports.splitName = function(nameDesc) {
// like: name, [name], name text, [name] text, name - text, or [name] - text
// the hyphen must be on the same line as the name; this prevents us from treating a Markdown
// dash as a separator
// optional values get special treatment
var result = null;
if (nameDesc[0] === '[') {
result = splitNameMatchingBrackets(nameDesc);
if (result !== null) {
return result;
}
}
nameDesc.match(REGEXP_NAME_DESCRIPTION);
return {
name: RegExp.$1,
description: RegExp.$2
};
};
@@ -0,0 +1,302 @@
/**
* Parse the command line arguments.
* @module jsdoc/opts/argparser
* @author Michael Mathews <micmath@gmail.com>
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var _ = require('underscore');
var util = require('util');
var hasOwnProp = Object.prototype.hasOwnProperty;
/**
* Create an instance of the parser.
* @classdesc A parser to interpret the key-value pairs entered on the command line.
* @constructor
* @alias module:jsdoc/opts/argparser
*/
var ArgParser = function() {
this._options = [];
this._shortNameIndex = {};
this._longNameIndex = {};
};
ArgParser.prototype._getOptionByShortName = function(name) {
if (hasOwnProp.call(this._shortNameIndex, name)) {
return this._options[this._shortNameIndex[name]];
}
return null;
};
ArgParser.prototype._getOptionByLongName = function(name) {
if (hasOwnProp.call(this._longNameIndex, name)) {
return this._options[this._longNameIndex[name]];
}
return null;
};
ArgParser.prototype._addOption = function(option) {
var currentIndex;
var longName = option.longName;
var shortName = option.shortName;
this._options.push(option);
currentIndex = this._options.length - 1;
if (shortName) {
this._shortNameIndex[shortName] = currentIndex;
}
if (longName) {
this._longNameIndex[longName] = currentIndex;
}
return this;
};
/**
* Provide information about a legal option.
* @param {character} shortName The short name of the option, entered like: -T.
* @param {string} longName The equivalent long name of the option, entered like: --test.
* @param {boolean} hasValue Does this option require a value? Like: -t templatename
* @param {string} helpText A brief description of the option.
* @param {boolean} [canHaveMultiple=false] Set to `true` if the option can be provided more than once.
* @param {function} [coercer] A function to coerce the given value to a specific type.
* @return {this}
* @example
* myParser.addOption('t', 'template', true, 'The path to the template.');
* myParser.addOption('h', 'help', false, 'Show the help message.');
*/
ArgParser.prototype.addOption = function(shortName, longName, hasValue, helpText, canHaveMultiple, coercer) {
var option = {
shortName: shortName,
longName: longName,
hasValue: hasValue,
helpText: helpText,
canHaveMultiple: (canHaveMultiple || false),
coercer: coercer
};
return this._addOption(option);
};
// TODO: refactor addOption to accept objects, then get rid of this method
/**
* Provide information about an option that should not cause an error if present, but that is always
* ignored (for example, an option that was used in previous versions but is no longer supported).
*
* @private
* @param {string} shortName - The short name of the option with a leading hyphen (for example,
* `-v`).
* @param {string} longName - The long name of the option with two leading hyphens (for example,
* `--version`).
*/
ArgParser.prototype.addIgnoredOption = function(shortName, longName) {
var option = {
shortName: shortName,
longName: longName,
ignore: true
};
return this._addOption(option);
};
function padding(length) {
return new Array(length + 1).join(' ');
}
function padLeft(str, length) {
return padding(length) + str;
}
function padRight(str, length) {
return str + padding(length);
}
function findMaxLength(arr) {
var max = 0;
arr.forEach(function(item) {
if (item.length > max) {
max = item.length;
}
});
return max;
}
function concatWithMaxLength(items, maxLength) {
var result = '';
// to prevent endless loops, always use the first item, regardless of length
result += items.shift();
while ( items.length && (result.length + items[0].length < maxLength) ) {
result += ' ' + items.shift();
}
return result;
}
// we want to format names and descriptions like this:
// | -f, --foo Very long description very long description very long |
// | description very long description. |
function formatHelpInfo(options) {
var MARGIN_LENGTH = 4;
var results = [];
var maxLength = process.stdout.columns;
var maxNameLength = findMaxLength(options.names);
var maxDescriptionLength = findMaxLength(options.descriptions);
var wrapDescriptionAt = maxLength - (MARGIN_LENGTH * 3) - maxNameLength;
// build the string for each option
options.names.forEach(function(name, i) {
var result;
var partialDescription;
var words;
// add a left margin to the name
result = padLeft(options.names[i], MARGIN_LENGTH);
// and a right margin, with extra padding so the descriptions line up with one another
result = padRight(result, maxNameLength - options.names[i].length + MARGIN_LENGTH);
// split the description on spaces
words = options.descriptions[i].split(' ');
// add as much of the description as we can fit on the first line
result += concatWithMaxLength(words, wrapDescriptionAt);
// if there's anything left, keep going until we've consumed the description
while (words.length) {
partialDescription = padding( maxNameLength + (MARGIN_LENGTH * 2) );
partialDescription += concatWithMaxLength(words, wrapDescriptionAt);
result += '\n' + partialDescription;
}
results.push(result);
});
return results;
}
/**
* Generate a summary of all the options with corresponding help text.
* @returns {string}
*/
ArgParser.prototype.help = function() {
var options = {
names: [],
descriptions: []
};
this._options.forEach(function(option) {
var name = '';
// don't show ignored options
if (option.ignore) {
return;
}
if (option.shortName) {
name += '-' + option.shortName + (option.longName ? ', ' : '');
}
if (option.longName) {
name += '--' + option.longName;
}
if (option.hasValue) {
name += ' <value>';
}
options.names.push(name);
options.descriptions.push(option.helpText);
});
return 'Options:\n' + formatHelpInfo(options).join('\n');
};
/**
* Get the options.
* @param {Array.<string>} args An array, like ['-x', 'hello']
* @param {Object} [defaults={}] An optional collection of default values.
* @returns {Object} The keys will be the longNames, or the shortName if no longName is defined for
* that option. The values will be the values provided, or `true` if the option accepts no value.
*/
ArgParser.prototype.parse = function(args, defaults) {
var result = defaults && _.defaults({}, defaults) || {};
result._ = [];
for (var i = 0, leni = args.length; i < leni; i++) {
var arg = '' + args[i],
next = (i < leni - 1) ? '' + args[i + 1] : null,
option,
shortName = null,
longName,
name,
value = null;
// like -t
if (arg.charAt(0) === '-') {
// like --template
if (arg.charAt(1) === '-') {
name = longName = arg.slice(2);
option = this._getOptionByLongName(longName);
}
else {
name = shortName = arg.slice(1);
option = this._getOptionByShortName(shortName);
}
if (option === null) {
throw new Error( util.format('Unknown command-line option "%s".', name) );
}
if (option.hasValue) {
value = next;
i++;
if (value === null || value.charAt(0) === '-') {
throw new Error( util.format('The command-line option "%s" requires a value.', name) );
}
}
else {
value = true;
}
// skip ignored options now that we've consumed the option text
if (option.ignore) {
continue;
}
if (option.longName && shortName) {
name = option.longName;
}
if (typeof option.coercer === 'function') {
value = option.coercer(value);
}
// Allow for multiple options of the same type to be present
if (option.canHaveMultiple && hasOwnProp.call(result, name)) {
var val = result[name];
if (val instanceof Array) {
val.push(value);
} else {
result[name] = [val, value];
}
}
else {
result[name] = value;
}
}
else {
result._.push(arg);
}
}
return result;
};
module.exports = ArgParser;
@@ -0,0 +1,141 @@
/**
* @module jsdoc/opts/args
* @requires jsdoc/opts/argparser
* @author Michael Mathews <micmath@gmail.com>
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var ArgParser = require('jsdoc/opts/argparser');
var querystring = require('querystring');
var util = require('util');
var ourOptions;
var argParser = new ArgParser();
var hasOwnProp = Object.prototype.hasOwnProperty;
// cast strings to booleans or integers where appropriate
function castTypes(item) {
var integer;
var result = item;
switch (result) {
case 'true':
result = true;
break;
case 'false':
result = false;
break;
default:
// might be an integer
integer = parseInt(result, 10);
if (String(integer) === result && integer !== 'NaN') {
result = integer;
}
}
return result;
}
// check for strings that we need to cast to other types
function fixTypes(item) {
var result = item;
// recursively process arrays and objects
if ( util.isArray(result) ) {
for (var i = 0, l = result.length; i < l; i++) {
result[i] = fixTypes(result[i]);
}
}
else if (typeof result === 'object') {
Object.keys(result).forEach(function(prop) {
result[prop] = fixTypes(result[prop]);
});
}
else {
result = castTypes(result);
}
return result;
}
function parseQuery(str) {
var result = querystring.parse(str);
Object.keys(result).forEach(function(prop) {
result[prop] = fixTypes(result[prop]);
});
return result;
}
argParser.addOption('t', 'template', true, 'The path to the template to use. Default: path/to/jsdoc/templates/default');
argParser.addOption('c', 'configure', true, 'The path to the configuration file. Default: path/to/jsdoc/conf.json');
argParser.addOption('e', 'encoding', true, 'Assume this encoding when reading all source files. Default: utf8');
argParser.addOption('T', 'test', false, 'Run all tests and quit.');
argParser.addOption('d', 'destination', true, 'The path to the output folder. Use "console" to dump data to the console. Default: ./out/');
argParser.addOption('p', 'private', false, 'Display symbols marked with the @private tag. Default: false');
argParser.addOption('r', 'recurse', false, 'Recurse into subdirectories when scanning for source code files.');
argParser.addOption('h', 'help', false, 'Print this message and quit.');
argParser.addOption('X', 'explain', false, 'Dump all found doclet internals to console and quit.');
argParser.addOption('q', 'query', true, 'A query string to parse and store in env.opts.query. Example: foo=bar&baz=true', false, parseQuery);
argParser.addOption('u', 'tutorials', true, 'Directory in which JSDoc should search for tutorials.');
argParser.addOption('P', 'package', true, 'The path to the project\'s package file. Default: path/to/sourcefiles/package.json');
argParser.addOption('R', 'readme', true, 'The path to the project\'s README file. Default: path/to/sourcefiles/README.md');
argParser.addOption('v', 'version', false, 'Display the version number and quit.');
argParser.addOption('', 'debug', false, 'Log information for debugging JSDoc. On Rhino, launches the debugger when passed as the first option.');
argParser.addOption('', 'verbose', false, 'Log detailed information to the console as JSDoc runs.');
argParser.addOption('', 'pedantic', false, 'Treat errors as fatal errors, and treat warnings as errors. Default: false');
// Options specific to tests
argParser.addOption(null, 'match', true, 'Only run tests containing <value>.', true);
argParser.addOption(null, 'nocolor', false, 'Do not use color in console output from tests.');
// Options that are no longer supported and should be ignored
argParser.addIgnoredOption('l', 'lenient'); // removed in JSDoc 3.3.0
/**
* Set the options for this app.
* @throws {Error} Illegal arguments will throw errors.
* @param {string|String[]} args The command line arguments for this app.
*/
exports.parse = function(args) {
args = args || [];
if (typeof args === 'string' || args.constructor === String) {
args = String(args).split(/\s+/g);
}
ourOptions = argParser.parse(args);
return ourOptions;
};
/**
* Retrieve help message for options.
*/
exports.help = function() {
return argParser.help();
};
/**
* Get a named option.
* @variation (name)
* @param {string} name The name of the option.
* @return {string} The value associated with the given name.
*//**
* Get all the options for this app.
* @return {Object} A collection of key/values representing all the options.
*/
exports.get = function(name) {
if (typeof name === 'undefined') {
return ourOptions;
}
else if ( hasOwnProp.call(ourOptions, name) ) {
return ourOptions[name];
}
};
@@ -0,0 +1,254 @@
'use strict';
var logger = require('jsdoc/util/logger');
/**
* Provides access to information about a JavaScript package.
*
* @module jsdoc/package
* @see https://www.npmjs.org/doc/files/package.json.html
*/
// Collect all of the license information from a `package.json` file.
function getLicenses(packageInfo) {
var licenses = packageInfo.licenses ? packageInfo.licenses.slice(0) : [];
if (packageInfo.license) {
licenses.push({ type: packageInfo.license });
}
return licenses;
}
/**
* Information about where to report bugs in the package.
*
* @typedef {Object} module:jsdoc/package.Package~BugInfo
* @property {string} email - The email address for reporting bugs.
* @property {string} url - The URL for reporting bugs.
*/
/**
* Information about a package's software license.
*
* @typedef {Object} module:jsdoc/package.Package~LicenseInfo
* @property {string} type - An identifier for the type of license.
* @property {string} url - The URL for the complete text of the license.
*/
/**
* Information about a package author or contributor.
*
* @typedef {Object} module:jsdoc/package.Package~PersonInfo
* @property {string} name - The person's full name.
* @property {string} email - The person's email address.
* @property {string} url - The URL of the person's website.
*/
/**
* Information about a package's version-control repository.
*
* @typedef {Object} module:jsdoc/package.Package~RepositoryInfo
* @property {string} type - The type of version-control system that the repository uses (for
* example, `git` or `svn`).
* @property {string} url - The URL for the repository.
*/
/**
* Information about a JavaScript package. JSDoc can extract package information from
* `package.json` files that follow the
* [npm specification](https://www.npmjs.org/doc/files/package.json.html).
*
* **Note**: JSDoc does not validate or normalize the contents of `package.json` files. If your
* `package.json` file does not follow the npm specification, some properties of the `Package`
* object may not use the format documented here.
*
* @class
* @param {string} json - The contents of the `package.json` file.
*/
exports.Package = function(json) {
var packageInfo;
/**
* The string identifier that is shared by all `Package` objects.
*
* @readonly
* @default
* @type {string}
*/
this.kind = 'package';
try {
packageInfo = JSON.parse(json || '{}');
}
catch (e) {
logger.error('Unable to parse the package file: %s', e.message);
packageInfo = {};
}
if (packageInfo.name) {
/**
* The package name.
*
* @type {string}
*/
this.name = packageInfo.name;
}
/**
* The unique longname for this `Package` object.
*
* @type {string}
*/
this.longname = this.kind + ':' + this.name;
if (packageInfo.author) {
/**
* The author of this package. Contains either a
* {@link module:jsdoc/package.Package~PersonInfo PersonInfo} object or a string with
* information about the author.
*
* @type {(module:jsdoc/package.Package~PersonInfo|string)}
* @since 3.3.0
*/
this.author = packageInfo.author;
}
if (packageInfo.bugs) {
/**
* Information about where to report bugs in the project. May contain a URL, as a string, or
* an object with more detailed information.
*
* @type {(string|module:jsdoc/package.Package~BugInfo)}
* @since 3.3.0
*/
this.bugs = packageInfo.bugs;
}
if (packageInfo.contributors) {
/**
* The contributors to this package.
*
* @type {Array.<(module:jsdoc/package.Package~PersonInfo|string)>}
* @since 3.3.0
*/
this.contributors = packageInfo.contributors;
}
if (packageInfo.dependencies) {
/**
* The dependencies for this package.
*
* @type {Object}
* @since 3.3.0
*/
this.dependencies = packageInfo.dependencies;
}
if (packageInfo.description) {
/**
* A brief description of the package.
*
* @type {string}
*/
this.description = packageInfo.description;
}
if (packageInfo.devDependencies) {
/**
* The development dependencies for this package.
*
* @type {Object}
* @since 3.3.0
*/
this.devDependencies = packageInfo.devDependencies;
}
if (packageInfo.engines) {
/**
* The JavaScript engines that this package supports. Each key is a string that identifies the
* engine (for example, `node`). Each value is a
* [semver](https://www.npmjs.org/doc/misc/semver.html)-compliant version number for the engine.
*
* @type {Object}
* @since 3.3.0
*/
this.engines = packageInfo.engines;
}
/**
* The source files associated with the package.
*
* New `Package` objects always contain an empty array, regardless of whether the `package.json`
* file includes a `files` property.
*
* After JSDoc parses your input files, it sets this property to a list of paths to your input
* files.
*
* @type {Array.<string>}
*/
this.files = [];
if (packageInfo.homepage) {
/**
* The URL for the package's homepage.
*
* @type {string}
* @since 3.3.0
*/
this.homepage = packageInfo.homepage;
}
if (packageInfo.keywords) {
/**
* Keywords to help users find the package.
*
* @type {Array.<string>}
* @since 3.3.0
*/
this.keywords = packageInfo.keywords;
}
if (packageInfo.license || packageInfo.licenses) {
/**
* The licenses used by this package. Combines information from the `package.json` file's
* `license` property and the deprecated `licenses` property.
*
* @type {Array.<module:jsdoc/package.Package~LicenseInfo>}
*/
this.licenses = getLicenses(packageInfo);
}
if (packageInfo.main) {
/**
* The module ID that provides the primary entry point to the package. For example, if your
* package is a CommonJS module, and the value of this property is `foo`, users should be able
* to load your module with `require('foo')`.
*
* @type {string}
* @since 3.3.0
*/
this.main = packageInfo.main;
}
if (packageInfo.repository) {
/**
* The version-control repository for the package.
*
* @type {module:jsdoc/package.Package~RepositoryInfo}
* @since 3.3.0
*/
this.repository = packageInfo.repository;
}
if (packageInfo.version) {
/**
* The [semver](https://www.npmjs.org/doc/misc/semver.html)-compliant version number of the
* package.
*
* @type {string}
* @since 3.2.0
*/
this.version = packageInfo.version;
}
};
+134
View File
@@ -0,0 +1,134 @@
/*global env: true */
/**
* Extended version of the standard `path` module.
* @module jsdoc/path
*/
'use strict';
var fs = require('fs');
var path = require('path');
var runtime = require('jsdoc/util/runtime');
function prefixReducer(previousPath, current) {
var currentPath = [];
// if previousPath is defined, but has zero length, there's no common prefix; move along
if (previousPath && !previousPath.length) {
return currentPath;
}
currentPath = path.resolve(global.env.pwd, current).split(path.sep) || [];
if (previousPath && currentPath.length) {
// remove chunks that exceed the previous path's length
currentPath = currentPath.slice(0, previousPath.length);
// if a chunk doesn't match the previous path, remove everything from that chunk on
for (var i = 0, l = currentPath.length; i < l; i++) {
if (currentPath[i] !== previousPath[i]) {
currentPath.splice(i, currentPath.length - i);
break;
}
}
}
return currentPath;
}
/**
* Find the common prefix for an array of paths. If there is a common prefix, a trailing separator
* is appended to the prefix. Relative paths are resolved relative to the current working directory.
*
* For example, assuming that the current working directory is `/Users/jsdoc`:
*
* + For the single path `foo/bar/baz/qux.js`, the common prefix is `foo/bar/baz/`.
* + For paths `foo/bar/baz/qux.js`, `foo/bar/baz/quux.js`, and `foo/bar/baz.js`, the common prefix
* is `/Users/jsdoc/foo/bar/`.
* + For paths `../jsdoc/foo/bar/baz/qux/quux/test.js`, `/Users/jsdoc/foo/bar/bazzy.js`, and
* `../../Users/jsdoc/foo/bar/foobar.js`, the common prefix is `/Users/jsdoc/foo/bar/`.
* + For paths `foo/bar/baz/qux.js` and `../../Library/foo/bar/baz.js`, there is no common prefix,
* and an empty string is returned.
*
* @param {Array.<string>} paths - The paths to search for a common prefix.
* @return {string} The common prefix, or an empty string if there is no common prefix.
*/
exports.commonPrefix = function(paths) {
var segments;
var prefix = '';
paths = paths || [];
// if there's only one path, its resolved dirname (plus a trailing slash) is the common prefix
if (paths.length === 1) {
prefix = path.resolve(global.env.pwd, paths[0]);
if ( path.extname(prefix) ) {
prefix = path.dirname(prefix);
}
prefix += path.sep;
}
else {
segments = paths.reduce(prefixReducer, undefined) || [];
// if there's anything left (other than a placeholder for a leading slash), add a
// placeholder for a trailing slash
if ( segments.length && (segments.length > 1 || segments[0] !== '') ) {
segments.push('');
}
prefix = segments.join(path.sep);
}
return prefix;
};
/**
* Retrieve the fully qualified path to the requested resource.
*
* If the resource path is specified as a relative path, JSDoc searches for the path in the
* directory where the JSDoc configuration file is located, then in the current working directory,
* and finally in the JSDoc directory.
*
* If the resource path is specified as a fully qualified path, JSDoc uses the path as-is.
*
* @param {string} filepath - The path to the requested resource. May be an absolute path; a path
* relative to the JSDoc directory; or a path relative to the current working directory.
* @param {string} [filename] - The filename of the requested resource.
* @return {string} The fully qualified path (or, on Rhino, a URI) to the requested resource.
* Includes the filename if one was provided.
*/
exports.getResourcePath = function(filepath, filename) {
var result = null;
function pathExists(_path) {
try {
fs.readdirSync(_path);
}
catch(e) {
return false;
}
return true;
}
// absolute paths are normalized by path.resolve on the first pass
[path.dirname(global.env.opts.configure || ''), env.pwd, env.dirname].forEach(function(_path) {
if (!result && _path) {
_path = path.resolve(_path, filepath);
if ( pathExists(_path) ) {
result = _path;
}
}
});
if (result) {
result = filename ? path.join(result, filename) : result;
}
return result;
};
Object.keys(path).forEach(function(member) {
exports[member] = path[member];
});
@@ -0,0 +1,53 @@
/*global app: true */
/**
* Utility functions to support the JSDoc plugin framework.
* @module jsdoc/plugins
*/
'use strict';
var logger = require('jsdoc/util/logger');
var path = require('jsdoc/path');
function addHandlers(handlers, parser) {
Object.keys(handlers).forEach(function(eventName) {
parser.on(eventName, handlers[eventName]);
});
}
exports.installPlugins = function(plugins, parser) {
var dictionary = require('jsdoc/tag/dictionary');
var eventName;
var plugin;
for (var i = 0, l = plugins.length; i < l; i++) {
plugin = require(plugins[i]);
// allow user-defined plugins to...
// ...register event handlers
if (plugin.handlers) {
addHandlers(plugin.handlers, parser);
}
// ...define tags
if (plugin.defineTags) {
plugin.defineTags(dictionary);
}
// ...add a Rhino node visitor (deprecated in JSDoc 3.3)
if (plugin.nodeVisitor) {
if ( !parser.addNodeVisitor ) {
logger.error('Unable to add the Rhino node visitor from %s, because JSDoc ' +
'is not using the Rhino JavaScript parser.', plugins[i]);
}
else {
parser.addNodeVisitor(plugin.nodeVisitor);
}
}
// ...add a Mozilla Parser API node visitor
if (plugin.astNodeVisitor) {
parser.addAstNodeVisitor(plugin.astNodeVisitor);
}
}
};
@@ -0,0 +1,26 @@
/*global env: true */
/**
* Make the contents of a README file available to include in the output.
* @module jsdoc/readme
* @author Michael Mathews <micmath@gmail.com>
* @author Ben Blank <ben.blank@gmail.com>
*/
'use strict';
var fs = require('jsdoc/fs'),
markdown = require('jsdoc/util/markdown');
/**
* @class
* @classdesc Represents a README file.
* @param {string} path - The filepath to the README.
*/
function ReadMe(path) {
var content = fs.readFileSync(path, env.opts.encoding),
parse = markdown.getParser();
this.html = parse(content);
}
module.exports = ReadMe;
+759
View File
@@ -0,0 +1,759 @@
/**
* @overview Schema for validating JSDoc doclets.
*
* @author Michael Mathews <micmath@gmail.com>
* @author Jeff Williams <jeffrey.l.williams@gmail.com>
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
* @see <http://tools.ietf.org/html/draft-zyp-json-schema-03>
*/
'use strict';
// JSON schema types
var ARRAY = 'array';
var BOOLEAN = 'boolean';
var INTEGER = 'integer';
var NULL = 'null';
var NUMBER = 'number';
var OBJECT = 'object';
var STRING = 'string';
var UNDEFINED = 'undefined';
var BOOLEAN_OPTIONAL = [BOOLEAN, NULL, UNDEFINED];
var STRING_OPTIONAL = [STRING, NULL, UNDEFINED];
var EVENT_REGEXP = /event\:[\S]+/;
var PACKAGE_REGEXP = /package\:[\S]+/;
// information about the code associated with a doclet
var META_SCHEMA = exports.META_SCHEMA = {
type: OBJECT,
optional: true,
additionalProperties: false,
properties: {
code: {
type: OBJECT,
additionalProperties: false,
properties: {
funcscope: {
type: STRING,
optional: true
},
id: {
type: STRING,
optional: true
},
name: {
type: STRING,
optional: true
},
node: {
type: OBJECT,
optional: true
},
paramnames: {
type: ARRAY,
optional: true,
uniqueItems: true,
items: {
type: STRING
}
},
type: {
type: STRING,
optional: true
},
value: {
optional: true
}
}
},
filename: {
title: 'The name of the file that contains the code associated with this doclet.',
type: STRING,
optional: true
},
lineno: {
title: 'The line number of the code associated with this doclet.',
type: NUMBER,
optional: true
},
path: {
title: 'The path in which the code associated with this doclet is located.',
type: STRING,
optional: true
},
range: {
title: 'The positions of the first and last characters of the code associated with ' +
'this doclet.',
type: ARRAY,
optional: true,
minItems: 2,
maxItems: 2,
items: {
type: NUMBER
}
},
vars: {
type: OBJECT
}
}
};
// type property containing type names
var TYPE_PROPERTY_SCHEMA = exports.TYPE_PROPERTY_SCHEMA = {
type: OBJECT,
additionalProperties: false,
properties: {
names: {
type: ARRAY,
minItems: 1,
items: {
type: STRING
}
},
// type parser output
parsedType: {
type: OBJECT,
additionalProperties: true
}
}
};
// enumeration properties
var ENUM_PROPERTY_SCHEMA = exports.ENUM_PROPERTY_SCHEMA = {
type: OBJECT,
additionalProperties: false,
properties: {
comment: {
type: STRING
},
defaultvalue: {
type: STRING_OPTIONAL,
optional: true
},
description: {
type: STRING_OPTIONAL,
optional: true
},
kind: {
type: STRING,
// TODO: get this from a real enum somewhere
enum: ['member']
},
longname: {
type: STRING
},
memberof: {
type: STRING,
optional: true
},
meta: META_SCHEMA,
name: {
type: STRING
},
// is this member nullable? (derived from the type expression)
nullable: {
type: BOOLEAN_OPTIONAL
},
// is this member optional? (derived from the type expression)
optional: {
type: BOOLEAN_OPTIONAL
},
scope: {
type: STRING,
// TODO: get this from a real enum somewhere
enum: ['static']
},
type: TYPE_PROPERTY_SCHEMA,
// can this member be provided more than once? (derived from the type expression)
variable: {
type: BOOLEAN_OPTIONAL
}
}
};
// function parameter, or object property defined with @property tag
var PARAM_SCHEMA = exports.PARAM_SCHEMA = {
type: OBJECT,
additionalProperties: false,
properties: {
// what is the default value for this parameter?
defaultvalue: {
type: STRING_OPTIONAL,
optional: true
},
// a description of the parameter
description: {
type: STRING_OPTIONAL,
optional: true
},
// what name does this parameter have within the function?
name: {
type: STRING
},
// can the value for this parameter be null?
nullable: {
type: BOOLEAN_OPTIONAL,
optional: true
},
// is a value for this parameter optional?
optional: {
type: BOOLEAN_OPTIONAL,
optional: true
},
// what are the types of value expected for this parameter?
type: TYPE_PROPERTY_SCHEMA,
// can this parameter be repeated?
variable: {
type: BOOLEAN_OPTIONAL,
optional: true
}
}
};
var DOCLET_SCHEMA = exports.DOCLET_SCHEMA = {
type: OBJECT,
additionalProperties: false,
properties: {
// what access privileges are allowed
access: {
type: STRING,
optional: true,
// TODO: define this as an enumeration elsewhere
enum: [
'private',
'protected'
]
},
alias: {
type: STRING,
optional: true
},
augments: {
type: ARRAY,
optional: true,
uniqueItems: true,
items: {
type: STRING
}
},
author: {
type: ARRAY,
optional: true,
items: {
type: STRING
}
},
borrowed: {
type: ARRAY,
optional: true,
uniqueItems: true,
items: {
type: OBJECT,
additionalProperties: false,
properties: {
// name of the target
as: {
type: STRING,
optional: true
},
// name of the source
from: {
type: STRING
}
}
}
},
// a description of the class that this constructor belongs to
classdesc: {
type: STRING,
optional: true
},
comment: {
type: STRING
},
copyright: {
type: STRING,
optional: true
},
defaultvalue: {
optional: true
},
defaultvaluetype: {
type: STRING,
optional: true,
enum: [OBJECT, ARRAY]
},
// is usage of this symbol deprecated?
deprecated: {
type: [STRING, BOOLEAN],
optional: true
},
// a description
description: {
type: STRING_OPTIONAL,
optional: true
},
// something else to consider
examples: {
type: ARRAY,
optional: true,
items: {
type: STRING
}
},
exceptions: {
type: ARRAY,
optional: true,
items: PARAM_SCHEMA
},
// the path to another constructor
extends: {
type: ARRAY,
optional: true,
uniqueItems: true,
items: {
type: STRING
}
},
// the path to another doc object
fires: {
type: ARRAY,
optional: true,
uniqueItems: true,
items: {
type: STRING,
pattern: EVENT_REGEXP
}
},
forceMemberof: {
type: BOOLEAN_OPTIONAL,
optional: true
},
ignore: {
type: BOOLEAN,
optional: true
},
implementations: {
type: ARRAY,
optional: true,
items: {
type: STRING
}
},
implements: {
type: ARRAY,
optional: true,
items: {
type: STRING
}
},
inheritdoc: {
type: STRING,
optional: true
},
inherited: {
type: BOOLEAN,
optional: true
},
inherits: {
type: STRING,
optional: true,
dependency: {
inherited: true
}
},
isEnum: {
type: BOOLEAN,
optional: true
},
// what kind of symbol is this?
kind: {
type: STRING,
// TODO: define this as an enumeration elsewhere
enum: [
'class',
'constant',
'event',
'external',
'file',
'function',
'interface',
'member',
'mixin',
'module',
'namespace',
'package',
'param',
'typedef'
]
},
license: {
type: STRING,
optional: true
},
listens: {
type: ARRAY,
optional: true,
uniqueItems: true,
items: {
type: STRING,
pattern: EVENT_REGEXP
}
},
longname: {
type: STRING
},
// probably a leading substring of the path
memberof: {
type: STRING,
optional: true
},
// information about this doc
meta: META_SCHEMA,
// was this doclet mixed in?
mixed: {
type: BOOLEAN,
optional: true
},
mixes: {
type: ARRAY,
optional: true,
uniqueItems: true,
items: {
type: STRING
}
},
// probably a trailing substring of the path
name: {
type: STRING
},
// is this member nullable? (derived from the type expression)
nullable: {
type: BOOLEAN_OPTIONAL
},
// is this member optional? (derived from the type expression)
optional: {
type: BOOLEAN_OPTIONAL
},
// does this member explicitly override the parent?
override: {
type: BOOLEAN,
optional: true
},
overrides: {
type: STRING,
optional: true
},
// are there function parameters associated with this doc?
params: {
type: ARRAY,
optional: true,
uniqueItems: true,
items: PARAM_SCHEMA
},
preserveName: {
type: BOOLEAN,
optional: true
},
properties: {
type: ARRAY,
optional: true,
uniqueItems: true,
minItems: 1,
items: {
anyOf: [ENUM_PROPERTY_SCHEMA, PARAM_SCHEMA]
}
},
readonly: {
type: BOOLEAN,
optional: true
},
// the symbol being documented requires another symbol
requires: {
type: ARRAY,
optional: true,
uniqueItems: true,
minItems: 1,
items: {
type: STRING
}
},
returns: {
type: ARRAY,
optional: true,
minItems: 1,
items: PARAM_SCHEMA
},
// what sort of parent scope does this symbol have?
scope: {
type: STRING,
enum: [
// TODO: make these an enumeration
'global',
'inner',
'instance',
'static'
]
},
// something else to consider
see: {
type: ARRAY,
optional: true,
minItems: 1,
items: {
type: STRING
}
},
// at what previous version was this doc added?
since: {
type: STRING,
optional: true
},
summary: {
type: STRING,
optional: true
},
// arbitrary tags associated with this doc
tags: {
type: ARRAY,
optional: true,
minItems: 1,
items: {
type: OBJECT,
additionalProperties: false,
properties: {
originalTitle: {
type: STRING
},
text: {
type: STRING,
optional: true
},
title: {
type: STRING
},
value: {
type: [STRING, OBJECT],
optional: true,
properties: PARAM_SCHEMA
}
}
}
},
'this': {
type: STRING,
optional: true
},
todo: {
type: ARRAY,
optional: true,
minItems: 1,
items: {
type: STRING
}
},
// extended tutorials
tutorials: {
type: ARRAY,
optional: true,
minItems: 1,
items: {
type: STRING
}
},
// what type is the value that this doc is associated with, like `number`
type: TYPE_PROPERTY_SCHEMA,
undocumented: {
type: BOOLEAN,
optional: true
},
// can this member be provided more than once? (derived from the type expression)
variable: {
type: BOOLEAN_OPTIONAL
},
variation: {
type: STRING,
optional: true
},
// what is the version of this doc
version: {
type: STRING,
optional: true
},
// is a member left to be implemented during inheritance?
virtual: {
type: BOOLEAN,
optional: true
},
// Platform support
platforms: {
type: ARRAY,
optional: true,
minItems: 1,
items: {
type: STRING
}
}
}
};
var CONTACT_INFO_SCHEMA = exports.CONTACT_INFO_SCHEMA = {
type: OBJECT,
additionalProperties: false,
properties: {
email: {
type: STRING,
optional: true
},
name: {
type: STRING,
optional: true
},
url: {
type: STRING,
optional: true,
format: 'uri'
}
}
};
var BUGS_SCHEMA = exports.BUGS_SCHEMA = {
type: OBJECT,
additionalProperties: false,
properties: {
email: {
type: STRING,
optional: true
},
url: {
type: STRING,
optional: true,
format: 'uri'
}
}
};
var PACKAGE_SCHEMA = exports.PACKAGE_SCHEMA = {
type: OBJECT,
additionalProperties: false,
properties: {
author: {
anyOf: [STRING, CONTACT_INFO_SCHEMA],
optional: true
},
bugs: {
anyOf: [STRING, BUGS_SCHEMA],
optional: true
},
contributors: {
type: ARRAY,
optional: true,
minItems: 0,
items: {
anyOf: [STRING, CONTACT_INFO_SCHEMA]
}
},
dependencies: {
type: OBJECT,
optional: true
},
description: {
type: STRING,
optional: true
},
devDependencies: {
type: OBJECT,
optional: true
},
engines: {
type: OBJECT,
optional: true
},
files: {
type: ARRAY,
uniqueItems: true,
minItems: 0,
items: {
type: STRING
}
},
homepage: {
type: STRING,
optional: true,
format: 'uri'
},
keywords: {
type: ARRAY,
optional: true,
minItems: 0,
items: {
type: STRING
}
},
kind: {
type: STRING,
enum: ['package']
},
licenses: {
type: ARRAY,
optional: true,
minItems: 1,
items: {
type: OBJECT,
additionalProperties: false,
properties: {
type: {
type: STRING,
optional: true
},
url: {
type: STRING,
optional: true,
format: 'uri'
}
}
}
},
longname: {
type: STRING,
optional: true,
pattern: PACKAGE_REGEXP
},
main: {
type: STRING,
optional: true
},
name: {
type: STRING,
optional: true
},
repository: {
type: OBJECT,
optional: true,
additionalProperties: false,
properties: {
type: {
type: STRING,
optional: true
},
// we don't use `format: 'uri'` here because repo URLs are atypical
url: {
type: STRING,
optional: true
}
}
},
version: {
type: STRING,
optional: true
}
}
};
var DOCLETS_SCHEMA = exports.DOCLETS_SCHEMA = {
type: ARRAY,
items: {
anyOf: [DOCLET_SCHEMA, PACKAGE_SCHEMA]
}
};
@@ -0,0 +1,419 @@
'use strict';
var esprima = require('esprima');
var jsdoc = {
src: {
syntax: require('jsdoc/src/syntax'),
Walker: require('jsdoc/src/walker').Walker
},
util: {
logger: require('jsdoc/util/logger')
}
};
var Syntax = jsdoc.src.syntax.Syntax;
// TODO: should set e.stopPropagation == true for consistency with Rhino, right?
var VISITOR_CONTINUE = true;
var VISITOR_STOP = false;
// TODO: docs; empty array means any node type, otherwise only the node types in the array
var acceptsLeadingComments = (function() {
var accepts = {};
// these nodes always accept leading comments
var commentable = [
Syntax.AssignmentExpression,
Syntax.CallExpression,
Syntax.FunctionDeclaration,
Syntax.FunctionExpression,
Syntax.MemberExpression,
Syntax.Property,
Syntax.TryStatement,
Syntax.VariableDeclaration,
Syntax.VariableDeclarator,
Syntax.WithStatement
];
for (var i = 0, l = commentable.length; i < l; i++) {
accepts[commentable[i]] = [];
}
// these nodes accept leading comments if they have specific types of parent nodes
// like: function foo(/** @type {string} */ bar) {}
accepts[Syntax.Identifier] = [
Syntax.CatchClause,
Syntax.FunctionDeclaration,
Syntax.FunctionExpression
];
// like: var Foo = Class.create(/** @lends Foo */{ // ... })
accepts[Syntax.ObjectExpression] = [
Syntax.CallExpression,
Syntax.Property,
Syntax.ReturnStatement
];
return accepts;
})();
// TODO: docs
function canAcceptComment(node) {
var canAccept = false;
var spec = acceptsLeadingComments[node.type];
if (spec) {
// empty array means we don't care about the parent type
if (spec.length === 0) {
canAccept = true;
}
// we can accept the comment if the spec contains the type of the node's parent
else if (node.parent) {
canAccept = spec.indexOf(node.parent.type) !== -1;
}
}
return canAccept;
}
// TODO: docs
// check whether node1 is before node2
function isBefore(beforeRange, afterRange) {
return beforeRange[1] <= afterRange[0];
}
// TODO: docs
function isWithin(innerRange, outerRange) {
return innerRange[0] >= outerRange[0] && innerRange[1] <= outerRange[1];
}
// TODO: docs
function isJsdocComment(comment) {
return comment && (comment.type === 'Block') && (comment.value[0] === '*');
}
/**
* Add the raw comment string to a block comment node.
*
* @private
* @param {!Object} comment - A comment node with `type` and `value` properties.
*/
function addRawComment(comment) {
comment.raw = comment.raw || ('/*' + comment.value + '*/');
return comment;
}
// TODO: docs
function scrubComments(comments) {
var comment;
var scrubbed = [];
for (var i = 0, l = comments.length; i < l; i++) {
comment = comments[i];
if ( isJsdocComment(comment) ) {
scrubbed.push( addRawComment(comment) );
}
}
return scrubbed;
}
// TODO: docs
var AstBuilder = exports.AstBuilder = function() {};
function parse(source, filename, esprimaOpts) {
var ast;
try {
ast = esprima.parse(source, esprimaOpts);
}
catch (e) {
jsdoc.util.logger.error('Unable to parse %s: %s', filename, e.message);
}
return ast;
}
// TODO: docs
AstBuilder.prototype.build = function(source, filename) {
var ast;
var esprimaOpts = {
comment: true,
loc: true,
range: true,
tokens: true
};
ast = parse(source, filename, esprimaOpts);
if (ast) {
this._postProcess(filename, ast);
}
return ast;
};
// TODO: docs
function atomSorter(a, b) {
var aRange = a.range;
var bRange = b.range;
var result = 0;
// does a end before b starts?
if ( isBefore(aRange, bRange) ) {
result = -1;
}
// does a enclose b?
else if ( isWithin(bRange, aRange) ) {
result = -1;
}
// does a start before b?
else if (aRange[0] < bRange[0]) {
result = -1;
}
// are the ranges non-identical? if so, b must be first
else if ( aRange[0] !== bRange[0] || aRange[1] !== bRange[1] ) {
result = 1;
}
return result;
}
// TODO: docs
// TODO: export?
function CommentAttacher(comments, tokens) {
this._comments = comments || [];
this._tokens = tokens || [];
this._tokenIndex = 0;
this._previousNode = null;
this._astRoot = null;
this._resetPendingComments()
._resetCandidates();
}
// TODO: docs
CommentAttacher.prototype._resetPendingComments = function() {
this._pendingComments = [];
this._pendingCommentRange = null;
return this;
};
// TODO: docs
CommentAttacher.prototype._resetCandidates = function() {
this._candidates = [];
return this;
};
// TODO: docs
CommentAttacher.prototype._nextComment = function() {
return this._comments[0] || null;
};
// TODO: docs
CommentAttacher.prototype._nextToken = function() {
return this._tokens[this._tokenIndex] || null;
};
// TODO: docs
// find the index of the atom whose end position is closest to (but not after) the specified
// position
CommentAttacher.prototype._nextIndexBefore = function(atoms, startIndex, position) {
var atom;
var newIndex = startIndex;
for (var i = newIndex, l = atoms.length; i < l; i++) {
atom = atoms[i];
if (atom.range[1] > position) {
break;
}
else {
newIndex = i;
}
}
return newIndex;
};
// TODO: docs
CommentAttacher.prototype._advanceTokenIndex = function(node) {
var position = node.range[0];
this._tokenIndex = this._nextIndexBefore(this._tokens, this._tokenIndex, position);
return this;
};
// TODO: docs
CommentAttacher.prototype._fastForwardComments = function(node) {
var position = node.range[0];
var commentIndex = this._nextIndexBefore(this._comments, 0, position);
// all comments before the node (except the last one) are pended
if (commentIndex > 0) {
this._pendingComments = this._pendingComments.concat( this._comments.splice(0,
commentIndex) );
}
};
CommentAttacher.prototype._attachPendingCommentsAsLeading = function(target) {
target.leadingComments = (target.leadingComments || []).concat(this._pendingComments);
};
CommentAttacher.prototype._attachPendingCommentsAsTrailing = function(target) {
target.trailingComments = (target.trailingComments || []).concat(this._pendingComments);
};
// TODO: docs
CommentAttacher.prototype._attachPendingComments = function(currentNode) {
var target;
if (!this._pendingComments.length) {
return this;
}
// if there are one or more candidate nodes, attach the pending comments before the last
// candidate node
if (this._candidates.length > 0) {
target = this._candidates[this._candidates.length - 1];
this._attachPendingCommentsAsLeading(target);
}
// if we don't have a previous node, attach pending comments before the AST root; this should
// mean that we haven't encountered any other nodes yet, or that the source file contains
// JSDoc comments but not code
else if (!this._previousNode) {
target = this._astRoot;
this._attachPendingCommentsAsLeading(target);
}
// otherwise, the comments must come after the current node (or the last node of the AST, if
// we've run out of nodes)
else {
this._attachPendingCommentsAsTrailing(currentNode || this._previousNode);
}
// update the previous node
this._previousNode = currentNode;
this._resetPendingComments()
._resetCandidates();
return this;
};
// TODO: docs
CommentAttacher.prototype._isEligible = function(node) {
var atoms;
var token;
var isEligible = false;
var comment = this._nextComment();
if (comment) {
atoms = [node, comment];
token = this._nextToken();
if (token) {
atoms.push(token);
}
atoms.sort(atomSorter);
// a candidate node must immediately follow the comment
if (atoms.indexOf(node) === atoms.indexOf(comment) + 1) {
isEligible = true;
}
}
return isEligible;
};
// TODO: docs
// TODO: do we ever get multiple candidate nodes?
CommentAttacher.prototype.visit = function(node) {
var isEligible;
// bail if we're out of comments
if ( !this._nextComment() ) {
return VISITOR_STOP;
}
// set the AST root if necessary
this._astRoot = this._astRoot || node;
// move to the next token, and fast-forward past comments that can no longer be attached
this._advanceTokenIndex(node);
this._fastForwardComments(node);
// now we can check whether the current node is in the right position to accept the next comment
isEligible = this._isEligible(node);
// attach the pending comments, if any
this._attachPendingComments(node);
// okay, now that we've done all that bookkeeping, we can check whether the current node accepts
// leading comments and add it to the candidate list if needed
if ( isEligible && canAcceptComment(node) ) {
// make sure we don't go past the end of the outermost target node
if (!this._pendingCommentRange) {
this._pendingCommentRange = node.range.slice(0);
}
this._candidates.push(node);
// we have a candidate node, so pend the current comment
this._pendingComments.push(this._comments.splice(0, 1)[0]);
}
return VISITOR_CONTINUE;
};
// TODO: docs
CommentAttacher.prototype.finish = function() {
var length = this._comments.length;
// any leftover comments are pended
if (length) {
this._pendingComments = this._pendingComments.concat( this._comments.splice(0, length) );
}
// attach the pending comments, if any
this._attachPendingComments();
};
// TODO: docs
// TODO: refactor to make this extensible
/**
* @private
* @param {string} filename - The full path to the source file.
* @param {Object} ast - An abstract syntax tree that conforms to the Mozilla Parser API.
*/
AstBuilder.prototype._postProcess = function(filename, ast) {
var attachComments = !!ast.comments && !!ast.comments.length;
var commentAttacher;
var scrubbed;
var visitor;
var walker;
if (!attachComments) {
return;
}
scrubbed = scrubComments(ast.comments.slice(0));
commentAttacher = new CommentAttacher(scrubbed.slice(0), ast.tokens);
visitor = {
visit: function(node) {
return commentAttacher.visit(node);
}
};
walker = new jsdoc.src.Walker();
walker.recurse(ast, visitor, filename);
commentAttacher.finish();
// replace the comments with the filtered comments
ast.comments = scrubbed;
// we no longer need the tokens
ast.tokens = [];
};
@@ -0,0 +1,299 @@
// TODO: docs
'use strict';
var Syntax = require('jsdoc/src/syntax').Syntax;
var util = require('util');
// Counter for generating unique node IDs.
var uid = 100000000;
/**
* Check whether an AST node represents a function.
*
* @param {Object} node - The AST node to check.
* @return {boolean} Set to `true` if the node is a function or `false` in all other cases.
*/
var isFunction = exports.isFunction = function(node) {
return node.type === Syntax.FunctionDeclaration || node.type === Syntax.FunctionExpression;
};
/**
* Check whether an AST node creates a new scope.
*
* @param {Object} node - The AST node to check.
* @return {Boolean} Set to `true` if the node creates a new scope, or `false` in all other cases.
*/
var isScope = exports.isScope = function(node) {
// TODO: handle blocks with "let" declarations
return !!node && typeof node === 'object' && ( node.type === Syntax.CatchClause ||
isFunction(node) );
};
// TODO: docs
var addNodeProperties = exports.addNodeProperties = function(node) {
var debugEnabled = !!global.env.opts.debug;
var newProperties = {};
if (!node || typeof node !== 'object') {
return null;
}
if (!node.nodeId) {
newProperties.nodeId = {
value: 'astnode' + uid++,
enumerable: debugEnabled
};
}
if (!node.parent && node.parent !== null) {
newProperties.parent = {
// `null` means 'no parent', so use `undefined` for now
value: undefined,
writable: true
};
}
if (!node.enclosingScope && node.enclosingScope !== null) {
newProperties.enclosingScope = {
// `null` means 'no enclosing scope', so use `undefined` for now
value: undefined,
writable: true
};
}
if (debugEnabled && !node.parentId) {
newProperties.parentId = {
enumerable: true,
get: function() {
return this.parent ? this.parent.nodeId : null;
}
};
}
if (debugEnabled && !node.enclosingScopeId) {
newProperties.enclosingScopeId = {
enumerable: true,
get: function() {
return this.enclosingScope ? this.enclosingScope.nodeId : null;
}
};
}
Object.defineProperties(node, newProperties);
return node;
};
// TODO: docs
var nodeToString = exports.nodeToString = function(node) {
var tempObject;
var str = '';
switch (node.type) {
case Syntax.ArrayExpression:
tempObject = [];
node.elements.forEach(function(el, i) {
// handle sparse arrays. use `null` to represent missing values, consistent with
// JSON.stringify([,]).
if (!el) {
tempObject[i] = null;
}
// preserve literal values so that the JSON form shows the correct type
else if (el.type === Syntax.Literal) {
tempObject[i] = el.value;
}
else {
tempObject[i] = nodeToString(el);
}
});
str = JSON.stringify(tempObject);
break;
case Syntax.AssignmentExpression:
str = nodeToString(node.left);
break;
case Syntax.FunctionDeclaration:
// falls through
case Syntax.FunctionExpression:
str = 'function';
break;
case Syntax.Identifier:
str = node.name;
break;
case Syntax.Literal:
str = String(node.value);
break;
case Syntax.MemberExpression:
// could be computed (like foo['bar']) or not (like foo.bar)
str = nodeToString(node.object);
if (node.computed) {
str += util.format('[%s]', node.property.raw);
}
else {
str += '.' + nodeToString(node.property);
}
break;
case Syntax.ObjectExpression:
tempObject = {};
node.properties.forEach(function(prop) {
var key = prop.key.name;
// preserve literal values so that the JSON form shows the correct type
if (prop.value.type === Syntax.Literal) {
tempObject[key] = prop.value.value;
}
else {
tempObject[key] = nodeToString(prop);
}
});
str = JSON.stringify(tempObject);
break;
case Syntax.ThisExpression:
str = 'this';
break;
case Syntax.UnaryExpression:
// like -1. in theory, operator can be prefix or postfix. in practice, any value with a
// valid postfix operator (such as -- or ++) is not a UnaryExpression.
str = nodeToString(node.argument);
// workaround for https://code.google.com/p/esprima/issues/detail?id=526
if (node.prefix === true || node.prefix === undefined) {
str = node.operator + str;
}
else {
// this shouldn't happen
throw new Error( util.format('Found a UnaryExpression with a postfix operator: %j',
node) );
}
break;
case Syntax.VariableDeclarator:
str = nodeToString(node.id);
break;
default:
str = '';
}
return str;
};
// TODO: docs
var getParamNames = exports.getParamNames = function(node) {
if (!node || !node.params) {
return [];
}
return node.params.map(function(param) {
return nodeToString(param);
});
};
// TODO: docs
var isAccessor = exports.isAccessor = function(node) {
return !!node && typeof node === 'object' && node.type === Syntax.Property &&
(node.kind === 'get' || node.kind === 'set');
};
// TODO: docs
var isAssignment = exports.isAssignment = function(node) {
return !!node && typeof node === 'object' && (node.type === Syntax.AssignmentExpression ||
node.type === Syntax.VariableDeclarator);
};
// TODO: docs
/**
* Retrieve information about the node, including its name and type.
*/
var getInfo = exports.getInfo = function(node) {
var info = {};
switch (node.type) {
// like: "foo = 'bar'" (after declaring foo)
// like: "MyClass.prototype.myMethod = function() {}" (after declaring MyClass)
case Syntax.AssignmentExpression:
info.node = node.right;
info.name = nodeToString(node.left);
info.type = info.node.type;
info.value = nodeToString(info.node);
// if the assigned value is a function, we need to capture the parameter names here
info.paramnames = getParamNames(node.right);
break;
// like: "function foo() {}"
case Syntax.FunctionDeclaration:
info.node = node;
info.name = nodeToString(node.id);
info.type = info.node.type;
info.paramnames = getParamNames(node);
break;
// like the function in: "var foo = function() {}"
case Syntax.FunctionExpression:
info.node = node;
// TODO: should we add a name for, e.g., "var foo = function bar() {}"?
info.name = '';
info.type = info.node.type;
info.paramnames = getParamNames(node);
break;
// like the param "bar" in: "function foo(bar) {}"
case Syntax.Identifier:
info.node = node;
info.name = nodeToString(info.node);
info.type = info.node.type;
break;
// like "a.b.c"
case Syntax.MemberExpression:
info.node = node;
info.name = nodeToString(info.node);
info.type = info.node.type;
break;
// like "a: 0" in "var foo = {a: 0}"
case Syntax.Property:
info.node = node.value;
info.name = nodeToString(node.key);
info.value = nodeToString(info.node);
if ( isAccessor(node) ) {
info.type = nodeToString(info.node);
info.paramnames = getParamNames(info.node);
}
else {
info.type = info.node.type;
}
break;
// like: "var i = 0" (has init property)
// like: "var i" (no init property)
case Syntax.VariableDeclarator:
info.node = node.init || node.id;
info.name = node.id.name;
if (node.init) {
info.type = info.node.type;
info.value = nodeToString(info.node);
}
break;
default:
info.node = node;
info.type = info.node.type;
}
return info;
};
@@ -0,0 +1,67 @@
/*global env: true */
/**
@module jsdoc/src/filter
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var path = require('jsdoc/path');
var pwd = env.pwd;
function makeRegExp(config) {
var regExp = null;
if (config) {
regExp = (typeof config === 'string') ? new RegExp(config) : config;
}
return regExp;
}
/**
@constructor
@param {object} opts
@param {string[]} opts.exclude - Specific files to exclude.
@param {string|RegExp} opts.includePattern
@param {string|RegExp} opts.excludePattern
*/
exports.Filter = function(opts) {
this.exclude = opts.exclude && Array.isArray(opts.exclude) ?
opts.exclude.map(function($) {
return path.resolve(pwd, $);
}) :
null;
this.includePattern = makeRegExp(opts.includePattern);
this.excludePattern = makeRegExp(opts.excludePattern);
};
/**
@param {string} filepath - The filepath to check.
@returns {boolean} Should the given file be included?
*/
exports.Filter.prototype.isIncluded = function(filepath) {
var included = true;
filepath = path.resolve(pwd, filepath);
if ( this.includePattern && !this.includePattern.test(filepath) ) {
included = false;
}
if ( this.excludePattern && this.excludePattern.test(filepath) ) {
included = false;
}
if (this.exclude) {
this.exclude.forEach(function(exclude) {
if ( filepath.indexOf(exclude) === 0 ) {
included = false;
}
});
}
return included;
};
@@ -0,0 +1,325 @@
/**
* @module jsdoc/src/handlers
*/
'use strict';
var escape = require('escape-string-regexp');
var jsdoc = {
doclet: require('jsdoc/doclet'),
name: require('jsdoc/name'),
util: {
logger: require('jsdoc/util/logger')
}
};
var util = require('util');
var currentModule = null;
var SCOPE_NAMES = jsdoc.name.SCOPE.NAMES;
var SCOPE_PUNC = jsdoc.name.SCOPE.PUNC;
var unresolvedName = /^((?:module.)?exports|this)(\.|$)/;
function CurrentModule(doclet) {
this.doclet = doclet;
this.longname = doclet.longname;
this.originalName = doclet.meta.code.name || '';
}
function filterByLongname(doclet) {
// you can't document prototypes
if ( /#$/.test(doclet.longname) ) {
return true;
}
return false;
}
function createDoclet(comment, e) {
var doclet;
var err;
try {
doclet = new jsdoc.doclet.Doclet(comment, e);
}
catch (error) {
err = new Error( util.format('cannot create a doclet for the comment "%s": %s',
comment.replace(/[\r\n]/g, ''), error.message) );
jsdoc.util.logger.error(err);
doclet = new jsdoc.doclet.Doclet('', e);
}
return doclet;
}
/**
* Create a doclet for a `symbolFound` event. The doclet represents an actual symbol that is defined
* in the code.
*
* Here's why this function is useful. A JSDoc comment can define a symbol name by including:
*
* + A `@name` tag
* + Another tag that accepts a name, such as `@function`
*
* When the JSDoc comment defines a symbol name, we treat it as a "virtual comment" for a symbol
* that isn't actually present in the code. And if a virtual comment is attached to a symbol, it's
* possible that the comment and symbol have nothing to do with one another.
*
* To handle this case, this function checks the new doclet to see if we've already added a name
* property by parsing the JSDoc comment. If so, this method creates a replacement doclet that
* ignores the attached JSDoc comment and only looks at the code.
*
* @private
*/
function createSymbolDoclet(comment, e) {
var doclet = createDoclet(comment, e);
if (doclet.name) {
// try again, without the comment
e.comment = '@undocumented';
doclet = createDoclet(e.comment, e);
}
return doclet;
}
function setCurrentModule(doclet) {
if (doclet.kind === 'module') {
currentModule = new CurrentModule(doclet);
}
}
function setModuleScopeMemberOf(doclet) {
// handle module symbols that are _not_ assigned to module.exports
if (currentModule && currentModule.longname !== doclet.name) {
// if we don't already know the scope, it must be an inner member
if (!doclet.scope) {
doclet.addTag('inner');
}
// if the doclet isn't a memberof anything yet, and it's not a global, it must be a memberof
// the current module
if (!doclet.memberof && doclet.scope !== SCOPE_NAMES.GLOBAL) {
doclet.addTag('memberof', currentModule.longname);
}
}
}
function setDefaultScope(doclet) {
// module doclets don't get a default scope
if (!doclet.scope && doclet.kind !== 'module') {
doclet.setScope(SCOPE_NAMES.GLOBAL);
}
}
function addDoclet(parser, newDoclet) {
var e;
if (newDoclet) {
setCurrentModule(newDoclet);
e = { doclet: newDoclet };
parser.emit('newDoclet', e);
if ( !e.defaultPrevented && !filterByLongname(e.doclet) ) {
parser.addResult(e.doclet);
}
}
}
function processAlias(parser, doclet, astNode) {
var memberofName;
if (doclet.alias === '{@thisClass}') {
memberofName = parser.resolveThis(astNode);
// "class" refers to the owner of the prototype, not the prototype itself
if ( /^(.+?)(\.prototype|#)$/.test(memberofName) ) {
memberofName = RegExp.$1;
}
doclet.alias = memberofName;
}
doclet.addTag('name', doclet.alias);
doclet.postProcess();
}
// TODO: separate code that resolves `this` from code that resolves the module object
function findSymbolMemberof(parser, doclet, astNode, nameStartsWith, trailingPunc) {
var memberof = '';
var nameAndPunc = nameStartsWith + (trailingPunc || '');
var scopePunc = '';
// remove stuff that indicates module membership (but don't touch the name `module.exports`,
// which identifies the module object itself)
if (doclet.name !== 'module.exports') {
doclet.name = doclet.name.replace(nameAndPunc, '');
}
// like `bar` in:
// exports.bar = 1;
// module.exports.bar = 1;
// module.exports = MyModuleObject; MyModuleObject.bar = 1;
if (nameStartsWith !== 'this' && currentModule && doclet.name !== 'module.exports') {
memberof = currentModule.longname;
scopePunc = SCOPE_PUNC.STATIC;
}
// like: module.exports = 1;
else if (doclet.name === 'module.exports' && currentModule) {
doclet.addTag('name', currentModule.longname);
doclet.postProcess();
}
else {
memberof = parser.resolveThis(astNode);
// like the following at the top level of a module:
// this.foo = 1;
if (nameStartsWith === 'this' && currentModule && !memberof) {
memberof = currentModule.longname;
scopePunc = SCOPE_PUNC.STATIC;
}
else {
scopePunc = SCOPE_PUNC.INSTANCE;
}
}
return {
memberof: memberof,
scopePunc: scopePunc
};
}
function addSymbolMemberof(parser, doclet, astNode) {
var basename;
var memberof;
var memberofInfo;
var moduleOriginalName = '';
var resolveTargetRegExp;
var scopePunc;
var unresolved;
if (!astNode) {
return;
}
// check to see if the doclet name is an unresolved reference to the module object, or to `this`
// TODO: handle cases where the module object is shadowed in the current scope
if (currentModule) {
moduleOriginalName = '|' + currentModule.originalName;
}
resolveTargetRegExp = new RegExp('^((?:module.)?exports|this' + moduleOriginalName +
')(\\.|$)');
unresolved = resolveTargetRegExp.exec(doclet.name);
if (unresolved) {
memberofInfo = findSymbolMemberof(parser, doclet, astNode, unresolved[1], unresolved[2]);
memberof = memberofInfo.memberof;
scopePunc = memberofInfo.scopePunc;
if (memberof) {
doclet.name = doclet.name ?
memberof + scopePunc + doclet.name :
memberof;
}
}
else {
memberofInfo = parser.astnodeToMemberof(astNode);
if ( Array.isArray(memberofInfo) ) {
basename = memberofInfo[1];
memberof = memberofInfo[0];
}
else {
memberof = memberofInfo;
}
}
// if we found a memberof name, apply it to the doclet
if (memberof) {
doclet.addTag('memberof', memberof);
if (basename) {
doclet.name = (doclet.name || '')
.replace(new RegExp('^' + escape(basename) + '.'), '');
}
}
// otherwise, add the defaults for a module (if we're currently in a module)
else {
setModuleScopeMemberOf(doclet);
}
}
function newSymbolDoclet(parser, docletSrc, e) {
var memberofName = null;
var newDoclet = createSymbolDoclet(docletSrc, e);
// if there's an alias, use that as the symbol name
if (newDoclet.alias) {
processAlias(parser, newDoclet, e.astnode);
}
// otherwise, get the symbol name from the code
else if (e.code && e.code.name) {
newDoclet.addTag('name', e.code.name);
if (!newDoclet.memberof) {
addSymbolMemberof(parser, newDoclet, e.astnode);
}
newDoclet.postProcess();
}
else {
return false;
}
// set the scope to global unless any of the following are true:
// a) the doclet is a memberof something
// b) the doclet represents a module
// c) we're in a module that exports only this symbol
if ( !newDoclet.memberof && newDoclet.kind !== 'module' &&
(!currentModule || currentModule.longname !== newDoclet.name) ) {
newDoclet.scope = SCOPE_NAMES.GLOBAL;
}
addDoclet(parser, newDoclet);
e.doclet = newDoclet;
}
/**
* Attach these event handlers to a particular instance of a parser.
* @param parser
*/
exports.attachTo = function(parser) {
// Handle JSDoc "virtual comments" that include one of the following:
// + A `@name` tag
// + Another tag that accepts a name, such as `@function`
parser.on('jsdocCommentFound', function(e) {
var comments = e.comment.split(/@also\b/g);
var newDoclet;
for (var i = 0, l = comments.length; i < l; i++) {
newDoclet = createDoclet(comments[i], e);
// we're only interested in virtual comments here
if (!newDoclet.name) {
continue;
}
// add the default scope/memberof for a module (if we're in a module)
setModuleScopeMemberOf(newDoclet);
newDoclet.postProcess();
// if we _still_ don't have a scope, use the default
setDefaultScope(newDoclet);
addDoclet(parser, newDoclet);
e.doclet = newDoclet;
}
});
// Handle named symbols in the code. May or may not have a JSDoc comment attached.
parser.on('symbolFound', function(e) {
var comments = e.comment.split(/@also\b/g);
for (var i = 0, l = comments.length; i < l; i++) {
newSymbolDoclet(parser, comments[i], e);
}
});
parser.on('fileComplete', function(e) {
currentModule = null;
});
};
@@ -0,0 +1,524 @@
/**
* @module jsdoc/src/parser
*/
'use strict';
var events = require('events');
var fs = require('jsdoc/fs');
var jsdoc = {
doclet: require('jsdoc/doclet'),
name: require('jsdoc/name'),
src: {
astnode: require('jsdoc/src/astnode'),
syntax: require('jsdoc/src/syntax')
},
util: {
doop: require('jsdoc/util/doop'),
runtime: require('jsdoc/util/runtime')
}
};
var logger = require('jsdoc/util/logger');
var path = require('jsdoc/path');
var util = require('util');
var hasOwnProp = Object.prototype.hasOwnProperty;
var Syntax = jsdoc.src.syntax.Syntax;
// TODO: docs
var PARSERS = exports.PARSERS = {
esprima: 'jsdoc/src/parser',
rhino: 'rhino/jsdoc/src/parser'
};
/*eslint-disable no-script-url */
// Prefix for JavaScript strings that were provided in lieu of a filename.
var SCHEMA = 'javascript:';
/*eslint-enable no-script-url */
// TODO: docs
exports.createParser = function(type) {
var modulePath;
if (!type) {
type = jsdoc.util.runtime.isRhino() ? 'rhino' : 'esprima';
}
if (PARSERS[type]) {
modulePath = PARSERS[type];
}
else {
modulePath = path.join( path.getResourcePath(path.dirname(type)), path.basename(type) );
}
try {
return new ( require(modulePath) ).Parser();
}
catch (e) {
logger.fatal('Unable to create the parser type "' + type + '": ' + e);
}
};
// TODO: docs
/**
* @class
* @alias module:jsdoc/src/parser.Parser
* @mixes module:events.EventEmitter
*
* @example <caption>Create a new parser.</caption>
* var jsdocParser = new (require('jsdoc/src/parser').Parser)();
*/
var Parser = exports.Parser = function(builderInstance, visitorInstance, walkerInstance) {
this.clear();
this._astBuilder = builderInstance || new (require('jsdoc/src/astbuilder')).AstBuilder();
this._visitor = visitorInstance || new (require('jsdoc/src/visitor')).Visitor(this);
this._walker = walkerInstance || new (require('jsdoc/src/walker')).Walker();
Object.defineProperties(this, {
astBuilder: {
get: function() {
return this._astBuilder;
}
},
visitor: {
get: function() {
return this._visitor;
}
},
walker: {
get: function() {
return this._walker;
}
}
});
};
util.inherits(Parser, events.EventEmitter);
// TODO: docs
Parser.prototype.clear = function() {
this._resultBuffer = [];
this.refs = {};
this.refs[jsdoc.name.LONGNAMES.GLOBAL] = {};
this.refs[jsdoc.name.LONGNAMES.GLOBAL].meta = {};
};
// TODO: update docs
/**
* Parse the given source files for JSDoc comments.
* @param {Array.<string>} sourceFiles An array of filepaths to the JavaScript sources.
* @param {string} [encoding=utf8]
*
* @fires module:jsdoc/src/parser.Parser.parseBegin
* @fires module:jsdoc/src/parser.Parser.fileBegin
* @fires module:jsdoc/src/parser.Parser.jsdocCommentFound
* @fires module:jsdoc/src/parser.Parser.symbolFound
* @fires module:jsdoc/src/parser.Parser.newDoclet
* @fires module:jsdoc/src/parser.Parser.fileComplete
* @fires module:jsdoc/src/parser.Parser.parseComplete
*
* @example <caption>Parse two source files.</caption>
* var myFiles = ['file1.js', 'file2.js'];
* var docs = jsdocParser.parse(myFiles);
*/
Parser.prototype.parse = function(sourceFiles, encoding) {
encoding = encoding || global.env.conf.encoding || 'utf8';
var filename = '';
var sourceCode = '';
var parsedFiles = [];
var e = {};
if (typeof sourceFiles === 'string') {
sourceFiles = [sourceFiles];
}
e.sourcefiles = sourceFiles;
logger.debug('Parsing source files: %j', sourceFiles);
this.emit('parseBegin', e);
for (var i = 0, l = sourceFiles.length; i < l; i++) {
sourceCode = '';
if (sourceFiles[i].indexOf(SCHEMA) === 0) {
sourceCode = sourceFiles[i].substr(SCHEMA.length);
filename = '[[string' + i + ']]';
}
else {
filename = sourceFiles[i];
try {
sourceCode = fs.readFileSync(filename, encoding);
}
catch(e) {
logger.error('Unable to read and parse the source file %s: %s', filename, e);
}
}
if (sourceCode.length) {
this._parseSourceCode(sourceCode, filename);
parsedFiles.push(filename);
}
}
this.emit('parseComplete', {
sourcefiles: parsedFiles,
doclets: this._resultBuffer
});
logger.debug('Finished parsing source files.');
return this._resultBuffer;
};
// TODO: docs
Parser.prototype.fireProcessingComplete = function(doclets) {
this.emit('processingComplete', { doclets: doclets });
};
// TODO: docs
Parser.prototype.results = function() {
return this._resultBuffer;
};
// TODO: update docs
/**
* @param {Object} o The parse result to add to the result buffer.
*/
Parser.prototype.addResult = function(o) {
this._resultBuffer.push(o);
};
// TODO: docs
Parser.prototype.addAstNodeVisitor = function(visitor) {
this._visitor.addAstNodeVisitor(visitor);
};
// TODO: docs
Parser.prototype.getAstNodeVisitors = function() {
return this._visitor.getAstNodeVisitors();
};
// TODO: docs
function pretreat(code) {
return code
// comment out hashbang at the top of the file, like: #!/usr/bin/env node
.replace(/^(\#\![\S \t]+\r?\n)/, '// $1')
// to support code minifiers that preserve /*! comments, treat /*!* as equivalent to /**
.replace(/\/\*\!\*/g, '/**')
// merge adjacent doclets
.replace(/\*\/\/\*\*+/g, '@also');
}
/** @private */
Parser.prototype._parseSourceCode = function(sourceCode, sourceName) {
var ast;
var globalScope;
var e = {
filename: sourceName
};
this.emit('fileBegin', e);
logger.printInfo('Parsing %s ...', sourceName);
if (!e.defaultPrevented) {
e = {
filename: sourceName,
source: sourceCode
};
this.emit('beforeParse', e);
sourceCode = e.source;
sourceName = e.filename;
sourceCode = pretreat(e.source);
ast = this._astBuilder.build(sourceCode, sourceName);
if (ast) {
this._walkAst(ast, this._visitor, sourceName);
}
}
this.emit('fileComplete', e);
logger.info('complete.');
};
/** @private */
Parser.prototype._walkAst = function(ast, visitor, sourceName) {
this._walker.recurse(ast, visitor, sourceName);
};
// TODO: docs
Parser.prototype.addDocletRef = function(e) {
var node;
if (e && e.code && e.code.node) {
node = e.code.node;
// allow lookup from value => doclet
if (e.doclet) {
this.refs[node.nodeId] = e.doclet;
}
// keep references to undocumented anonymous functions, too, as they might have scoped vars
else if (
(node.type === Syntax.FunctionDeclaration || node.type === Syntax.FunctionExpression) &&
!this.refs[node.nodeId] ) {
this.refs[node.nodeId] = {
longname: jsdoc.name.LONGNAMES.ANONYMOUS,
meta: {
code: e.code
}
};
}
}
};
// TODO: docs
Parser.prototype._getDoclet = function(id) {
if ( hasOwnProp.call(this.refs, id) ) {
return this.refs[id];
}
return null;
};
// TODO: docs
/**
* @param {string} name - The symbol's longname.
* @return {string} The symbol's basename.
*/
Parser.prototype.getBasename = function(name) {
if (name !== undefined) {
return name.replace(/^([$a-z_][$a-z_0-9]*).*?$/i, '$1');
}
};
// TODO: docs
function definedInScope(doclet, basename) {
return !!doclet && !!doclet.meta && !!doclet.meta.vars && !!basename &&
hasOwnProp.call(doclet.meta.vars, basename);
}
// TODO: docs
/**
* Given a node, determine what the node is a member of.
* @param {node} node
* @returns {string} The long name of the node that this is a member of.
*/
Parser.prototype.astnodeToMemberof = function(node) {
var basename;
var doclet;
var scope;
var result = '';
var type = node.type;
if ( (type === Syntax.FunctionDeclaration || type === Syntax.FunctionExpression ||
type === Syntax.VariableDeclarator) && node.enclosingScope ) {
doclet = this._getDoclet(node.enclosingScope.nodeId);
if (!doclet) {
result = jsdoc.name.LONGNAMES.ANONYMOUS + jsdoc.name.SCOPE.PUNC.INNER;
}
else {
result = (doclet.longname || doclet.name) + jsdoc.name.SCOPE.PUNC.INNER;
}
}
else {
// check local references for aliases
scope = node;
basename = this.getBasename( jsdoc.src.astnode.nodeToString(node) );
// walk up the scope chain until we find the scope in which the node is defined
while (scope.enclosingScope) {
doclet = this._getDoclet(scope.enclosingScope.nodeId);
if ( doclet && definedInScope(doclet, basename) ) {
result = [doclet.meta.vars[basename], basename];
break;
}
else {
// move up
scope = scope.enclosingScope;
}
}
// do we know that it's a global?
doclet = this.refs[jsdoc.name.LONGNAMES.GLOBAL];
if ( doclet && definedInScope(doclet, basename) ) {
result = [doclet.meta.vars[basename], basename];
}
// have we seen the node's parent? if so, use that
else if (node.parent) {
doclet = this._getDoclet(node.parent.nodeId);
// set the result if we found a doclet. (if we didn't, the AST node may describe a
// global symbol.)
if (doclet) {
result = doclet.longname || doclet.name;
}
}
}
return result;
};
// TODO: docs
/**
* Resolve what "this" refers to relative to a node.
* @param {node} node - The "this" node
* @returns {string} The longname of the enclosing node.
*/
Parser.prototype.resolveThis = function(node) {
var doclet;
var result;
// In general, if there's an enclosing scope, we use the enclosing scope to resolve `this`.
// For object properties, we use the node's parent (the object) instead.
if (node.type !== Syntax.Property && node.enclosingScope) {
doclet = this._getDoclet(node.enclosingScope.nodeId);
if (!doclet) {
result = jsdoc.name.LONGNAMES.ANONYMOUS; // TODO handle global this?
}
else if (doclet['this']) {
result = doclet['this'];
}
// like: Foo.constructor = function(n) { /** blah */ this.name = n; }
else if (doclet.kind === 'function' && doclet.memberof) {
result = doclet.memberof;
}
// like: var foo = function(n) { /** blah */ this.bar = n; }
else if ( doclet.kind === 'member' && jsdoc.src.astnode.isAssignment(node) ) {
result = doclet.longname || doclet.name;
}
// walk up to the closest class we can find
else if (doclet.kind === 'class' || doclet.kind === 'module') {
result = doclet.longname || doclet.name;
}
else if (node.enclosingScope) {
result = this.resolveThis(node.enclosingScope);
}
}
else if (node.parent) {
doclet = this.refs[node.parent.nodeId];
// TODO: is this behavior correct? when do we get here?
if (!doclet) {
result = ''; // global?
}
else {
result = doclet.longname || doclet.name;
}
}
// TODO: is this behavior correct? when do we get here?
else {
result = ''; // global?
}
return result;
};
/**
* Given an AST node representing an object property, find the doclets for the parent object or
* objects.
*
* If the object is part of a simple assignment (for example, `var foo = { x: 1 }`), this method
* returns a single doclet (in this case, the doclet for `foo`).
*
* If the object is part of a chained assignment (for example, `var foo = exports.FOO = { x: 1 }`,
* this method returns multiple doclets (in this case, the doclets for `foo` and `exports.FOO`).
*
* @param {Object} node - An AST node representing an object property.
* @return {Array.<jsdoc/doclet.Doclet>} An array of doclets for the parent object or objects, or
* an empty array if no doclets are found.
*/
Parser.prototype.resolvePropertyParents = function(node) {
var currentAncestor = node.parent;
var nextAncestor = currentAncestor ? currentAncestor.parent : null;
var doclet;
var doclets = [];
while (currentAncestor) {
doclet = this._getDoclet(currentAncestor.nodeId);
if (doclet) {
doclets.push(doclet);
}
// if the next ancestor is an assignment expression (for example, `exports.FOO` in
// `var foo = exports.FOO = { x: 1 }`, keep walking upwards
if (nextAncestor && nextAncestor.type === Syntax.AssignmentExpression) {
nextAncestor = nextAncestor.parent;
currentAncestor = currentAncestor.parent;
}
// otherwise, we're done
else {
currentAncestor = null;
}
}
return doclets;
};
// TODO: docs
/**
* Resolve what function a var is limited to.
* @param {astnode} node
* @param {string} basename The leftmost name in the long name: in foo.bar.zip the basename is foo.
*/
Parser.prototype.resolveVar = function(node, basename) {
var doclet;
var result;
var scope = node.enclosingScope;
// HACK: return an empty string for function declarations so they don't end up in anonymous
// scope (see #685 and #693)
if (node.type === Syntax.FunctionDeclaration) {
result = '';
}
else if (!scope) {
result = ''; // global
}
else {
doclet = this._getDoclet(scope.nodeId);
if ( definedInScope(doclet, basename) ) {
result = doclet.longname;
}
else {
result = this.resolveVar(scope, basename);
}
}
return result;
};
// TODO: docs
Parser.prototype.resolveEnum = function(e) {
var doclets = this.resolvePropertyParents(e.code.node.parent);
doclets.forEach(function(doclet) {
if (doclet && doclet.isEnum) {
doclet.properties = doclet.properties || [];
// members of an enum inherit the enum's type
if (doclet.type && !e.doclet.type) {
// clone the type to prevent circular refs
e.doclet.type = jsdoc.util.doop(doclet.type);
}
delete e.doclet.undocumented;
e.doclet.defaultvalue = e.doclet.meta.code.value;
// add the doclet to the parent's properties
doclet.properties.push(e.doclet);
}
});
};
// TODO: document other events
/**
* Fired once for each JSDoc comment in the current source code.
* @event jsdocCommentFound
* @memberof module:jsdoc/src/parser.Parser
* @type {Object}
* @property {string} comment The text content of the JSDoc comment
* @property {number} lineno The line number associated with the found comment.
* @property {string} filename The file name associated with the found comment.
*/
@@ -0,0 +1,70 @@
/*global env: true */
/**
@module jsdoc/src/scanner
@requires module:fs
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var fs = require('jsdoc/fs');
var logger = require('jsdoc/util/logger');
var path = require('jsdoc/path');
/**
@constructor
@mixes module:events
*/
exports.Scanner = function() {};
exports.Scanner.prototype = Object.create( require('events').EventEmitter.prototype );
/**
Recursively searches the given searchPaths for js files.
@param {Array.<string>} searchPaths
@param {number} [depth=1]
@fires sourceFileFound
*/
exports.Scanner.prototype.scan = function(searchPaths, depth, filter) {
var currentFile;
var isFile;
var filePaths = [];
var pwd = env.pwd;
var self = this;
searchPaths = searchPaths || [];
depth = depth || 1;
searchPaths.forEach(function($) {
var filepath = path.resolve( pwd, decodeURIComponent($) );
try {
currentFile = fs.statSync(filepath);
}
catch (e) {
logger.error('Unable to find the source file or directory %s', filepath);
return;
}
if ( currentFile.isFile() ) {
filePaths.push(filepath);
}
else {
filePaths = filePaths.concat( fs.ls(filepath, depth) );
}
});
filePaths = filePaths.filter(function($) {
return filter.isIncluded($);
});
filePaths = filePaths.filter(function($) {
var e = { fileName: $ };
self.emit('sourceFileFound', e);
return !e.defaultPrevented;
});
return filePaths;
};
@@ -0,0 +1,67 @@
'use strict';
// TODO: docs
exports.Syntax = {
ArrayExpression: 'ArrayExpression',
ArrayPattern: 'ArrayPattern',
ArrowFunctionExpression: 'ArrowFunctionExpression',
AssignmentExpression: 'AssignmentExpression',
BinaryExpression: 'BinaryExpression',
BlockStatement: 'BlockStatement',
BreakStatement: 'BreakStatement',
CallExpression: 'CallExpression',
CatchClause: 'CatchClause',
ClassBody: 'ClassBody',
ClassDeclaration: 'ClassDeclaration',
ClassExpression: 'ClassExpression',
ComprehensionBlock: 'ComprehensionBlock',
ComprehensionExpression: 'ComprehensionExpression',
ConditionalExpression: 'ConditionalExpression',
ContinueStatement: 'ContinueStatement',
DebuggerStatement: 'DebuggerStatement',
DoWhileStatement: 'DoWhileStatement',
EmptyStatement: 'EmptyStatement',
ExportBatchSpecifier: 'ExportBatchSpecifier',
ExportDeclaration: 'ExportDeclaration',
ExportSpecifier: 'ExportSpecifier',
ExpressionStatement: 'ExpressionStatement',
ForInStatement: 'ForInStatement',
ForOfStatement: 'ForOfStatement',
ForStatement: 'ForStatement',
FunctionDeclaration: 'FunctionDeclaration',
FunctionExpression: 'FunctionExpression',
Identifier: 'Identifier',
IfStatement: 'IfStatement',
ImportDeclaration: 'ImportDeclaration',
ImportSpecifier: 'ImportSpecifier',
LabeledStatement: 'LabeledStatement',
LetStatement: 'LetStatement', // TODO: update Rhino to use VariableDeclaration
Literal: 'Literal',
LogicalExpression: 'LogicalExpression',
MemberExpression: 'MemberExpression',
MethodDefinition: 'MethodDefinition',
ModuleDeclaration: 'ModuleDeclaration',
NewExpression: 'NewExpression',
ObjectExpression: 'ObjectExpression',
ObjectPattern: 'ObjectPattern',
Program: 'Program',
Property: 'Property',
ReturnStatement: 'ReturnStatement',
SequenceExpression: 'SequenceExpression',
SpreadElement: 'SpreadElement',
SwitchCase: 'SwitchCase',
SwitchStatement: 'SwitchStatement',
TaggedTemplateExpression: 'TaggedTemplateExpression',
TemplateElement: 'TemplateElement',
TemplateLiteral: 'TemplateLiteral',
ThisExpression: 'ThisExpression',
ThrowStatement: 'ThrowStatement',
TryStatement: 'TryStatement',
UnaryExpression: 'UnaryExpression',
UpdateExpression: 'UpdateExpression',
VariableDeclaration: 'VariableDeclaration',
VariableDeclarator: 'VariableDeclarator',
WhileStatement: 'WhileStatement',
WithStatement: 'WithStatement',
YieldExpression: 'YieldExpression'
};
@@ -0,0 +1,426 @@
/**
* @module jsdoc/src/visitor
*/
'use strict';
// TODO: consider exporting more stuff so users can override it
var jsdoc = {
doclet: require('jsdoc/doclet'),
name: require('jsdoc/name'),
src: {
astnode: require('jsdoc/src/astnode'),
syntax: require('jsdoc/src/syntax')
},
util: {
logger: require('jsdoc/util/logger')
}
};
var util = require('util');
var hasOwnProp = Object.prototype.hasOwnProperty;
var Syntax = jsdoc.src.syntax.Syntax;
// TODO: docs
function getLeadingComment(node) {
var comment = null;
var leadingComments = node.leadingComments;
if (Array.isArray(leadingComments) && leadingComments.length && leadingComments[0].raw) {
comment = leadingComments[0].raw;
}
return comment;
}
// TODO: docs
function makeVarsFinisher(scopeDoclet) {
return function(e) {
// no need to evaluate all things related to scopeDoclet again, just use it
if (scopeDoclet && e.doclet && e.doclet.alias) {
scopeDoclet.meta.vars[e.code.name] = e.doclet.longname;
}
};
}
/**
* For function parameters that have inline documentation, create a function that will merge the
* inline documentation into the function's doclet. If the parameter is already documented in the
* function's doclet, the inline documentation will be ignored.
*
* @private
* @param {module:jsdoc/src/parser.Parser} parser - The JSDoc parser.
* @return {function} A function that merges a parameter's inline documentation into the function's
* doclet.
*/
function makeInlineParamsFinisher(parser) {
return function(e) {
var documentedParams;
var knownParams;
var param;
var parentDoclet;
var i = 0;
if (e.doclet && e.doclet.meta && e.doclet.meta.code && e.doclet.meta.code.node &&
e.doclet.meta.code.node.parent) {
parentDoclet = parser._getDoclet(e.doclet.meta.code.node.parent.nodeId);
}
if (!parentDoclet) {
return;
}
// we only want to use the doclet if it's param-specific (but not, for example, if it's
// a param tagged with `@exports` in an AMD module)
if (e.doclet.kind !== 'param') {
return;
}
parentDoclet.params = parentDoclet.params || [];
documentedParams = parentDoclet.params;
knownParams = parentDoclet.meta.code.paramnames;
while (true) {
param = documentedParams[i];
// is the param already documented? if so, we don't need to use the doclet
if (param && param.name === e.doclet.name) {
e.doclet.undocumented = true;
break;
}
// if we ran out of documented params, or we're at the parameter's actual position,
// splice in the param at the current index
if ( !param || i === knownParams.indexOf(e.doclet.name) ) {
documentedParams.splice(i, 0, {
type: e.doclet.type,
description: '',
name: e.doclet.name
});
// the doclet is no longer needed
e.doclet.undocumented = true;
break;
}
i++;
}
};
}
// TODO: docs
function SymbolFound(node, filename, extras) {
var self = this;
extras = extras || {};
this.id = extras.id || node.nodeId;
this.comment = extras.comment || getLeadingComment(node) || '@undocumented';
this.lineno = extras.lineno || node.loc.start.line;
this.range = extras.range || node.range;
this.filename = extras.filename || filename;
this.astnode = extras.astnode || node;
this.code = extras.code;
this.event = extras.event || 'symbolFound';
this.finishers = extras.finishers || [];
// make sure the event includes properties that don't have default values
Object.keys(extras).forEach(function(key) {
self[key] = extras[key];
});
}
// TODO: docs
function JsdocCommentFound(comment, filename) {
this.comment = comment.raw;
this.lineno = comment.loc.start.line;
this.filename = filename;
this.range = comment.range;
Object.defineProperty(this, 'event', {
value: 'jsdocCommentFound'
});
}
// TODO: docs
var Visitor = exports.Visitor = function(parser) {
this._parser = parser;
// Mozilla Parser API node visitors added by plugins
this._nodeVisitors = [];
// built-in visitors
this._visitors = [
this.visitNodeComments,
this.visitNode
];
};
// TODO: docs
Visitor.prototype.addAstNodeVisitor = function(visitor) {
this._nodeVisitors.push(visitor);
};
// TODO: docs
Visitor.prototype.removeAstNodeVisitor = function(visitor) {
var idx = this._nodeVisitors.indexOf(visitor);
if (idx !== -1) {
this._nodeVisitors.splice(idx, 1);
}
};
// TODO: docs
Visitor.prototype.getAstNodeVisitors = function() {
return this._nodeVisitors;
};
// TODO: docs; visitor signature is (node, parser, filename)
Visitor.prototype.visit = function(node, filename) {
var i;
var l;
for (i = 0, l = this._visitors.length; i < l; i++) {
this._visitors[i].call(this, node, this._parser, filename);
}
return true;
};
// TODO: docs
/**
* Verify that a block comment exists and that its leading delimiter does not contain three or more
* asterisks.
*
* @private
* @memberof module:jsdoc/src/parser.Parser
*/
function isValidJsdoc(commentSrc) {
return commentSrc && commentSrc.indexOf('/***') !== 0;
}
// TODO: docs
function hasJsdocComments(node) {
return (node && node.leadingComments && node.leadingComments.length) ||
(node && node.trailingComments && node.trailingComments.length);
}
// TODO: docs
function removeCommentDelimiters(comment) {
return comment.substring(2, comment.length - 2);
}
// TODO: docs
function updateCommentNode(commentNode, comment) {
commentNode.raw = comment;
commentNode.value = removeCommentDelimiters(comment);
}
// TODO: docs
Visitor.prototype.visitNodeComments = function(node, parser, filename) {
var comment;
var comments;
var e;
var BLOCK_COMMENT = 'Block';
if ( !hasJsdocComments(node) && (!node.type || node.type !== BLOCK_COMMENT) ) {
return true;
}
comments = (node.type === BLOCK_COMMENT) ? [node] : [];
if (node.leadingComments && node.leadingComments.length) {
comments = comments.concat( node.leadingComments.slice(0) );
}
if (node.trailingComments && node.trailingComments.length) {
comments = comments.concat( node.trailingComments.slice(0) );
}
for (var i = 0, l = comments.length; i < l; i++) {
comment = comments[i];
if ( isValidJsdoc(comment.raw) ) {
e = new JsdocCommentFound(comment, filename);
parser.emit(e.event, e, parser);
if (e.comment !== comment.raw) {
updateCommentNode(comment, e.comment);
}
}
}
return true;
};
// TODO: docs
Visitor.prototype.visitNode = function(node, parser, filename) {
var i;
var l;
var e = this.makeSymbolFoundEvent(node, parser, filename);
if (this._nodeVisitors && this._nodeVisitors.length) {
for (i = 0, l = this._nodeVisitors.length; i < l; i++) {
this._nodeVisitors[i].visitNode(node, e, parser, filename);
if (e.stopPropagation) {
break;
}
}
}
if (!e.preventDefault && e.comment && isValidJsdoc(e.comment)) {
parser.emit(e.event, e, parser);
}
// add the node to the parser's lookup table
parser.addDocletRef(e);
for (i = 0, l = e.finishers.length; i < l; i++) {
e.finishers[i].call(parser, e);
}
return true;
};
// TODO: docs
// TODO: note that it's essential to call this function before you try to resolve names!
function trackVars(parser, node, e) {
var enclosingScopeId = node.enclosingScope ? node.enclosingScope.nodeId :
jsdoc.name.LONGNAMES.GLOBAL;
var doclet = parser.refs[enclosingScopeId];
if (doclet) {
doclet.meta.vars = doclet.meta.vars || {};
doclet.meta.vars[e.code.name] = null;
e.finishers.push( makeVarsFinisher(doclet) );
}
}
// TODO: docs
Visitor.prototype.makeSymbolFoundEvent = function(node, parser, filename) {
var logger = jsdoc.util.logger;
var e;
var basename;
var i;
var l;
var parent;
var extras = {
code: jsdoc.src.astnode.getInfo(node)
};
switch (node.type) {
// like: i = 0;
case Syntax.AssignmentExpression:
e = new SymbolFound(node, filename, extras);
trackVars(parser, node, e);
basename = parser.getBasename(e.code.name);
if (basename !== 'this') {
e.code.funcscope = parser.resolveVar(node, basename);
}
break;
// like: function foo() {}
case Syntax.FunctionDeclaration:
// falls through
// like: var foo = function() {};
case Syntax.FunctionExpression:
e = new SymbolFound(node, filename, extras);
trackVars(parser, node, e);
basename = parser.getBasename(e.code.name);
e.code.funcscope = parser.resolveVar(node, basename);
break;
// like "bar" in: function foo(/** @type {string} */ bar) {}
// or "module" in: define("MyModule", function(/** @exports MyModule */ module) {}
// This is an extremely common type of node; we only care about function parameters with
// inline comments. No need to fire an event unless the node is already commented.
case Syntax.Identifier:
parent = node.parent;
if ( node.leadingComments && parent && jsdoc.src.astnode.isFunction(parent) ) {
extras.finishers = [makeInlineParamsFinisher(parser)];
e = new SymbolFound(node, filename, extras);
trackVars(parser, node, e);
}
break;
// like "obj.prop" in: /** @typedef {string} */ obj.prop;
// Closure Compiler uses this pattern extensively for enums.
// No need to fire an event unless the node is already commented.
case Syntax.MemberExpression:
if (node.leadingComments) {
e = new SymbolFound(node, filename, extras);
}
break;
// like the object literal in: function Foo = Class.create(/** @lends Foo */ {});
case Syntax.ObjectExpression:
e = new SymbolFound(node, filename, extras);
break;
// like "bar: true" in: var foo = { bar: true };
// like "get bar() {}" in: var foo = { get bar() {} };
case Syntax.Property:
if ( node.kind !== ('get' || 'set') ) {
extras.finishers = [parser.resolveEnum];
}
e = new SymbolFound(node, filename, extras);
break;
// like: var i = 0;
case Syntax.VariableDeclarator:
e = new SymbolFound(node, filename, extras);
trackVars(parser, node, e);
basename = parser.getBasename(e.code.name);
break;
// for now, log a warning for all ES6 nodes, since we don't do anything useful with them
case Syntax.ArrowFunctionExpression:
case Syntax.ClassBody:
case Syntax.ClassDeclaration:
case Syntax.ClassExpression:
case Syntax.ExportBatchSpecifier:
case Syntax.ExportDeclaration:
case Syntax.ExportSpecifier:
case Syntax.ImportDeclaration:
case Syntax.ImportSpecifier:
case Syntax.MethodDefinition:
case Syntax.ModuleDeclaration:
case Syntax.SpreadElement:
case Syntax.TaggedTemplateExpression:
case Syntax.TemplateElement:
case Syntax.TemplateLiteral:
logger.warn('JSDoc does not currently handle %s nodes. Source file: %s, line %s',
node.type, filename, (node.loc && node.loc.start) ? node.loc.start.line : '??');
break;
default:
// ignore
}
if (!e) {
e = {
finishers: []
};
}
return e;
};
@@ -0,0 +1,539 @@
/**
* Traversal utilities for ASTs that are compatible with the Mozilla Parser API. Adapted from
* [Acorn](http://marijnhaverbeke.nl/acorn/).
*
* @module jsdoc/src/walker
* @license MIT
*/
'use strict';
var astnode = require('jsdoc/src/astnode');
var doclet = require('jsdoc/doclet');
var Syntax = require('jsdoc/src/syntax').Syntax;
/**
* Check whether an AST node creates a new scope.
*
* @private
* @param {Object} node - The AST node to check.
* @return {Boolean} Set to `true` if the node creates a new scope, or `false` in all other cases.
*/
function isScopeNode(node) {
// TODO: handle blocks with "let" declarations
return node && typeof node === 'object' && (node.type === Syntax.CatchClause ||
node.type === Syntax.FunctionDeclaration || node.type === Syntax.FunctionExpression);
}
// TODO: docs
function getCurrentScope(scopes) {
return scopes[scopes.length - 1] || null;
}
// TODO: docs
function moveComments(source, target) {
if (source.leadingComments) {
target.leadingComments = source.leadingComments.slice(0);
source.leadingComments = [];
}
}
function leafNode(node, parent, state, cb) {}
// TODO: docs
var walkers = exports.walkers = {};
walkers[Syntax.ArrayExpression] = function arrayExpression(node, parent, state, cb) {
for (var i = 0, l = node.elements.length; i < l; i++) {
var e = node.elements[i];
if (e) {
cb(e, node, state);
}
}
};
// TODO: verify correctness
walkers[Syntax.ArrayPattern] = function arrayPattern(node, parent, state, cb) {
for (var i = 0, l = node.elements.length; i < l; i++) {
var e = node.elements[i];
// must be an identifier or an expression
if (e && e.type !== Syntax.Identifier) {
cb(e, node, state);
}
}
};
walkers[Syntax.ArrowFunctionExpression] =
function arrowFunctionExpression(node, parent, state, cb) {
var i;
var l;
// used for function declarations, so we include it here
if (node.id) {
cb(node.id, node, state);
}
for (i = 0, l = node.params.length; i < l; i++) {
cb(node.params[i], node, state);
}
for (i = 0, l = node.defaults.length; i < l; i++) {
cb(node.defaults[i], node, state);
}
cb(node.body, node, state);
if (node.rest) {
cb(node.rest, node, state);
}
};
walkers[Syntax.AssignmentExpression] = function assignmentExpression(node, parent, state, cb) {
cb(node.left, node, state);
cb(node.right, node, state);
};
walkers[Syntax.BinaryExpression] = function binaryExpression(node, parent, state, cb) {
cb(node.left, node, state);
cb(node.right, node, state);
};
walkers[Syntax.BlockStatement] = function blockStatement(node, parent, state, cb) {
for (var i = 0, l = node.body.length; i < l; i++) {
cb(node.body[i], node, state);
}
};
walkers[Syntax.BreakStatement] = leafNode;
walkers[Syntax.CallExpression] = function callExpression(node, parent, state, cb) {
var i;
var l;
cb(node.callee, node, state);
if (node.arguments) {
for (i = 0, l = node.arguments.length; i < l; i++) {
cb(node.arguments[i], node, state);
}
}
};
walkers[Syntax.CatchClause] = leafNode;
walkers[Syntax.ClassBody] = walkers[Syntax.BlockStatement];
walkers[Syntax.ClassDeclaration] = function classDeclaration(node, parent, state, cb) {
if (node.id) {
cb(node.id, node, state);
}
if (node.superClass) {
cb(node.superClass, node, state);
}
if (node.body) {
cb(node.body, node, state);
}
};
walkers[Syntax.ClassExpression] = walkers[Syntax.ClassDeclaration];
// TODO: verify correctness
walkers[Syntax.ComprehensionBlock] = walkers[Syntax.AssignmentExpression];
// TODO: verify correctness
walkers[Syntax.ComprehensionExpression] =
function comprehensionExpression(node, parent, state, cb) {
cb(node.body, node, state);
if (node.filter) {
cb(node.filter, node, state);
}
for (var i = 0, l = node.blocks.length; i < l; i++) {
cb(node.blocks[i], node, state);
}
};
walkers[Syntax.ConditionalExpression] = function conditionalExpression(node, parent, state, cb) {
cb(node.test, node, state);
cb(node.consequent, node, state);
cb(node.alternate, node, state);
};
walkers[Syntax.ContinueStatement] = leafNode;
walkers[Syntax.DebuggerStatement] = leafNode;
walkers[Syntax.DoWhileStatement] = function doWhileStatement(node, parent, state, cb) {
cb(node.test, node, state);
cb(node.body, node, state);
};
walkers[Syntax.EmptyStatement] = leafNode;
walkers[Syntax.ExportBatchSpecifier] = leafNode;
walkers[Syntax.ExportDeclaration] = function exportDeclaration(node, parent, state, cb) {
var i;
var l;
if (node.declaration) {
for (i = 0, l = node.declaration.length; i < l; i++) {
cb(node.declaration[i], node, state);
}
}
if (node.specifiers) {
for (i = 0, l = node.specifiers.length; i < l; i++) {
cb(node.specifiers[i], node, state);
}
}
if (node.source) {
cb(node.source, node, state);
}
};
walkers[Syntax.ExportSpecifier] = function exportSpecifier(node, parent, state, cb) {
if (node.id) {
cb(node.id, node, state);
}
if (node.name) {
cb(node.name, node, state);
}
};
walkers[Syntax.ExpressionStatement] = function expressionStatement(node, parent, state, cb) {
cb(node.expression, node, state);
};
walkers[Syntax.ForInStatement] = function forInStatement(node, parent, state, cb) {
cb(node.left, node, state);
cb(node.right, node, state);
cb(node.body, node, state);
};
walkers[Syntax.ForOfStatement] = walkers[Syntax.ForInStatement];
walkers[Syntax.ForStatement] = function forStatement(node, parent, state, cb) {
if (node.init) {
cb(node.init, node, state);
}
if (node.test) {
cb(node.test, node, state);
}
if (node.update) {
cb(node.update, node, state);
}
cb(node.body, node, state);
};
walkers[Syntax.FunctionDeclaration] = walkers[Syntax.ArrowFunctionExpression];
walkers[Syntax.FunctionExpression] = walkers[Syntax.ArrowFunctionExpression];
walkers[Syntax.Identifier] = leafNode;
walkers[Syntax.IfStatement] = function ifStatement(node, parent, state, cb) {
cb(node.test, node, state);
cb(node.consequent, node, state);
if (node.alternate) {
cb(node.alternate, node, state);
}
};
walkers[Syntax.ImportDeclaration] = function importDeclaration(node, parent, state, cb) {
var i;
var l;
if (node.specifiers) {
for (i = 0, l = node.specifiers.length; i < l; i++) {
cb(node.specifiers[i], node, state);
}
}
if (node.source) {
cb(node.source, node, state);
}
};
walkers[Syntax.ImportSpecifier] = walkers[Syntax.ExportSpecifier];
walkers[Syntax.LabeledStatement] = function labeledStatement(node, parent, state, cb) {
cb(node.body, node, state);
};
// TODO: add scope info??
walkers[Syntax.LetStatement] = function letStatement(node, parent, state, cb) {
for (var i = 0, l = node.head.length; i < l; i++) {
var head = node.head[i];
cb(head.id, node, state);
if (head.init) {
cb(head.init, node, state);
}
}
cb(node.body, node, state);
};
walkers[Syntax.Literal] = leafNode;
walkers[Syntax.LogicalExpression] = walkers[Syntax.BinaryExpression];
walkers[Syntax.MemberExpression] = function memberExpression(node, parent, state, cb) {
cb(node.object, node, state);
if (node.property) {
cb(node.property, node, state);
}
};
walkers[Syntax.MethodDefinition] = function methodDefinition(node, parent, state, cb) {
if (node.key) {
cb(node.key, node, state);
}
if (node.value) {
cb(node.value, node, state);
}
};
walkers[Syntax.ModuleDeclaration] = function moduleDeclaration(node, parent, state, cb) {
if (node.id) {
cb(node.id, node, state);
}
if (node.source) {
cb(node.source, node, state);
}
if (node.body) {
cb(node.body, node, state);
}
};
walkers[Syntax.NewExpression] = walkers[Syntax.CallExpression];
walkers[Syntax.ObjectExpression] = function objectExpression(node, parent, state, cb) {
for (var i = 0, l = node.properties.length; i < l; i++) {
cb(node.properties[i], node, state);
}
};
walkers[Syntax.ObjectPattern] = walkers[Syntax.ObjectExpression];
walkers[Syntax.Program] = walkers[Syntax.BlockStatement];
walkers[Syntax.Property] = function property(node, parent, state, cb) {
// move leading comments from key to property node
moveComments(node.key, node);
cb(node.value, node, state);
};
walkers[Syntax.ReturnStatement] = function returnStatement(node, parent, state, cb) {
if (node.argument) {
cb(node.argument, node, state);
}
};
walkers[Syntax.SequenceExpression] = function sequenceExpression(node, parent, state, cb) {
for (var i = 0, l = node.expressions.length; i < l; i++) {
cb(node.expressions[i], node, state);
}
};
walkers[Syntax.SpreadElement] = function spreadElement(node, parent, state, cb) {
if (node.argument) {
cb(node.argument, node, state);
}
};
walkers[Syntax.SwitchCase] = function switchCase(node, parent, state, cb) {
if (node.test) {
cb(node.test, node, state);
}
for (var i = 0, l = node.consequent.length; i < l; i++) {
cb(node.consequent[i], node, state);
}
};
walkers[Syntax.SwitchStatement] = function switchStatement(node, parent, state, cb) {
cb(node.discriminant, node, state);
for (var i = 0, l = node.cases.length; i < l; i++) {
cb(node.cases[i], node, state);
}
};
walkers[Syntax.TaggedTemplateExpression] =
function taggedTemplateExpression(node, parent, state, cb) {
if (node.tag) {
cb(node.tag, node, state);
}
if (node.quasi) {
cb(node.quasi, node, state);
}
};
walkers[Syntax.TemplateElement] = leafNode;
walkers[Syntax.TemplateLiteral] = function templateLiteral(node, parent, state, cb) {
var i;
var l;
if (node.quasis && node.quasis.length) {
for (i = 0, l = node.quasis.length; i < l; i++) {
cb(node.quasis[i], node, state);
}
}
if (node.expressions && node.expressions.length) {
for (i = 0, l = node.expressions.length; i < l; i++) {
cb(node.expressions[i], node, state);
}
}
};
walkers[Syntax.ThisExpression] = leafNode;
walkers[Syntax.ThrowStatement] = function throwStatement(node, parent, state, cb) {
cb(node.argument, node, state);
};
walkers[Syntax.TryStatement] = function tryStatement(node, parent, state, cb) {
var i;
var l;
cb(node.block, node, state);
// handle Esprima ASTs, which deviate from the spec a bit
if ( node.handlers && Array.isArray(node.handlers) && node.handlers[0] ) {
cb(node.handlers[0].body, node, state);
}
else if (node.handler) {
cb(node.handler.body, node, state);
}
if (node.guardedHandlers) {
for (i = 0, l = node.guardedHandlers.length; i < l; i++) {
cb(node.guardedHandlers[i].body, node, state);
}
}
if (node.finalizer) {
cb(node.finalizer, node, state);
}
};
walkers[Syntax.UnaryExpression] = function unaryExpression(node, parent, state, cb) {
cb(node.argument, node, state);
};
walkers[Syntax.UpdateExpression] = walkers[Syntax.UnaryExpression];
walkers[Syntax.VariableDeclaration] = function variableDeclaration(node, parent, state, cb) {
// move leading comments to first declarator
moveComments(node, node.declarations[0]);
for (var i = 0, l = node.declarations.length; i < l; i++) {
cb(node.declarations[i], node, state);
}
};
walkers[Syntax.VariableDeclarator] = function variableDeclarator(node, parent, state, cb) {
cb(node.id, node, state);
if (node.init) {
cb(node.init, node, state);
}
};
walkers[Syntax.WhileStatement] = walkers[Syntax.DoWhileStatement];
walkers[Syntax.WithStatement] = function withStatement(node, parent, state, cb) {
cb(node.object, node, state);
cb(node.body, node, state);
};
walkers[Syntax.YieldExpression] = function(node, parent, state, cb) {
if (node.argument) {
cb(node.argument, node, state);
}
};
/**
* Create a walker that can traverse an AST that is consistent with the Mozilla Parser API.
*
* @todo docs
* @memberof module:jsdoc/src/walker
*/
var Walker = exports.Walker = function(walkerFuncs) {
this._walkers = walkerFuncs || walkers;
};
// TODO: docs
Walker.prototype._recurse = function(filename, ast) {
var self = this;
var state = {
filename: filename,
nodes: [],
scopes: []
};
function cb(node, parent, state) {
var currentScope;
var isScope = astnode.isScope(node);
// for efficiency, if the node has a `parent` property, assume that we've already
// added the required properties
if (typeof node.parent !== 'undefined') {
astnode.addNodeProperties(node);
}
node.parent = parent || null;
currentScope = getCurrentScope(state.scopes);
if (currentScope) {
node.enclosingScope = currentScope;
}
if (isScope) {
state.scopes.push(node);
}
state.nodes.push(node);
self._walkers[node.type](node, parent, state, cb);
if (isScope) {
state.scopes.pop();
}
}
cb(ast, null, state);
return state;
};
// TODO: docs
// TODO: skip the AST root node to be consistent with Rhino?
Walker.prototype.recurse = function(ast, visitor, filename) {
var shouldContinue;
var state = this._recurse(filename, ast);
if (visitor) {
for (var i = 0, l = state.nodes.length; i < l; i++) {
shouldContinue = visitor.visit.call(visitor, state.nodes[i], filename);
if (!shouldContinue) {
break;
}
}
}
return ast;
};
+196
View File
@@ -0,0 +1,196 @@
/*global env: true */
/**
@overview
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
/**
Functionality related to JSDoc tags.
@module jsdoc/tag
@requires jsdoc/tag/dictionary
@requires jsdoc/tag/validator
@requires jsdoc/tag/type
*/
'use strict';
var jsdoc = {
tag: {
dictionary: require('jsdoc/tag/dictionary'),
validator: require('jsdoc/tag/validator'),
type: require('jsdoc/tag/type')
},
util: {
logger: require('jsdoc/util/logger')
}
};
var path = require('jsdoc/path');
var util = require('util');
// Check whether the text is the same as a symbol name with leading or trailing whitespace. If so,
// the whitespace must be preserved, and the text cannot be trimmed.
function mustPreserveWhitespace(text, meta) {
return meta && meta.code && meta.code.name === text && text.match(/(?:^\s+)|(?:\s+$)/);
}
function trim(text, opts, meta) {
var indentMatcher;
var match;
opts = opts || {};
text = text || '';
if ( mustPreserveWhitespace(text, meta) ) {
text = util.format('"%s"', text);
}
else if (opts.keepsWhitespace) {
text = text.replace(/^[\n\r\f]+|[\n\r\f]+$/g, '');
if (opts.removesIndent) {
match = text.match(/^([ \t]+)/);
if (match && match[1]) {
indentMatcher = new RegExp('^' + match[1], 'gm');
text = text.replace(indentMatcher, '');
}
}
}
else {
text = text.replace(/^\s+|\s+$/g, '');
}
return text;
}
function addHiddenProperty(obj, propName, propValue) {
Object.defineProperty(obj, propName, {
value: propValue,
writable: true,
enumerable: !!global.env.opts.debug,
configurable: true
});
}
function parseType(tag, tagDef, meta) {
try {
return jsdoc.tag.type.parse(tag.text, tagDef.canHaveName, tagDef.canHaveType);
}
catch (e) {
jsdoc.util.logger.error(
'Unable to parse a tag\'s type expression%s with tag title "%s" and text "%s": %s',
meta.filename ? ( ' for source file ' + path.join(meta.path, meta.filename) ) : '',
tag.originalTitle,
tag.text,
e.message
);
return {};
}
}
function processTagText(tag, tagDef, meta) {
var tagType;
if (tagDef.onTagText) {
tag.text = tagDef.onTagText(tag.text);
}
if (tagDef.canHaveType || tagDef.canHaveName) {
/** The value property represents the result of parsing the tag text. */
tag.value = {};
tagType = parseType(tag, tagDef, meta);
// It is possible for a tag to *not* have a type but still have
// optional or defaultvalue, e.g. '@param [foo]'.
// Although tagType.type.length == 0 we should still copy the other properties.
if (tagType.type) {
if (tagType.type.length) {
tag.value.type = {
names: tagType.type
};
addHiddenProperty(tag.value.type, 'parsedType', tagType.parsedType);
}
['optional', 'nullable', 'variable', 'defaultvalue'].forEach(function(prop) {
if (typeof tagType[prop] !== 'undefined') {
tag.value[prop] = tagType[prop];
}
});
}
if (tagType.text && tagType.text.length) {
tag.value.description = tagType.text;
}
if (tagDef.canHaveName) {
// note the dash is a special case: as a param name it means "no name"
if (tagType.name && tagType.name !== '-') { tag.value.name = tagType.name; }
}
}
else {
tag.value = tag.text;
}
}
/**
* Replace the existing tag dictionary with a new tag dictionary.
*
* Used for testing only. Do not call this method directly. Instead, call
* {@link module:jsdoc/doclet._replaceDictionary}, which also updates this module's tag dictionary.
*
* @private
* @param {module:jsdoc/tag/dictionary.Dictionary} dict - The new tag dictionary.
*/
exports._replaceDictionary = function _replaceDictionary(dict) {
jsdoc.tag.dictionary = dict;
};
/**
Constructs a new tag object. Calls the tag validator.
@class
@classdesc Represents a single doclet tag.
@param {string} tagTitle
@param {string=} tagBody
@param {object=} meta
*/
var Tag = exports.Tag = function(tagTitle, tagBody, meta) {
var tagDef;
var trimOpts;
meta = meta || {};
this.originalTitle = trim(tagTitle);
/** The title of the tag (for example, `title` in `@title text`). */
this.title = jsdoc.tag.dictionary.normalise(this.originalTitle);
tagDef = jsdoc.tag.dictionary.lookUp(this.title);
trimOpts = {
keepsWhitespace: tagDef.keepsWhitespace,
removesIndent: tagDef.removesIndent
};
/**
* The text following the tag (for example, `text` in `@title text`).
*
* Whitespace is trimmed from the tag text as follows:
*
* + If the tag's `keepsWhitespace` option is falsy, all leading and trailing whitespace are
* removed.
* + If the tag's `keepsWhitespace` option is set to `true`, leading and trailing whitespace are
* not trimmed, unless the `removesIndent` option is also enabled.
* + If the tag's `removesIndent` option is set to `true`, any indentation that is shared by
* every line in the string is removed. This option is ignored unless `keepsWhitespace` is set
* to `true`.
*
* **Note**: If the tag text is the name of a symbol, and the symbol's name includes leading or
* trailing whitespace (for example, the property names in `{ ' ': true, ' foo ': false }`),
* the tag text is not trimmed. Instead, the tag text is wrapped in double quotes to prevent the
* whitespace from being trimmed.
*/
this.text = trim(tagBody, trimOpts, meta);
if (this.text) {
processTagText(this, tagDef, meta);
}
jsdoc.tag.validator.validate(this, tagDef, meta);
};
@@ -0,0 +1,119 @@
/** @module jsdoc/tag/dictionary */
'use strict';
var definitions = require('jsdoc/tag/dictionary/definitions');
var hasOwnProp = Object.prototype.hasOwnProperty;
var dictionary;
/** @private */
function TagDefinition(dictionary, title, etc) {
var self = this;
etc = etc || {};
this.title = dictionary.normalise(title);
Object.defineProperty(this, '_dictionary', {
value: dictionary
});
Object.keys(etc).forEach(function(p) {
self[p] = etc[p];
});
}
/** @private */
TagDefinition.prototype.synonym = function(synonymName) {
this._dictionary.defineSynonym(this.title, synonymName);
return this; // chainable
};
/**
* @class
* @alias module:jsdoc/tag/dictionary.Dictionary
*/
function Dictionary() {
this._tags = {};
this._tagSynonyms = {};
// The longnames for `Package` objects include a `package` namespace. There's no `package` tag,
// though, so we declare the namespace here.
this._namespaces = ['package'];
}
/** @function */
Dictionary.prototype._defineNamespace = function defineNamespace(title) {
title = this.normalise(title || '');
if (title && this._namespaces.indexOf(title) === -1) {
this._namespaces.push(title);
}
return this;
};
/** @function */
Dictionary.prototype.defineTag = function defineTag(title, opts) {
var tagDef = new TagDefinition(this, title, opts);
this._tags[tagDef.title] = tagDef;
if (opts && opts.isNamespace) {
this._defineNamespace(tagDef.title);
}
return this._tags[tagDef.title];
};
/** @function */
Dictionary.prototype.defineSynonym = function defineSynonym(title, synonym) {
this._tagSynonyms[synonym.toLowerCase()] = this.normalise(title);
};
/** @function */
Dictionary.prototype.getNamespaces = function getNamespaces() {
return this._namespaces.slice(0);
};
/** @function */
Dictionary.prototype.lookUp = function lookUp(title) {
title = this.normalise(title);
if ( hasOwnProp.call(this._tags, title) ) {
return this._tags[title];
}
return false;
};
/** @function */
Dictionary.prototype.isNamespace = function isNamespace(kind) {
if (kind) {
kind = this.normalise(kind);
if (this._namespaces.indexOf(kind) !== -1) {
return true;
}
}
return false;
};
/** @function */
Dictionary.prototype.normalise = function normalise(title) {
var canonicalName = title.toLowerCase();
if ( hasOwnProp.call(this._tagSynonyms, canonicalName) ) {
return this._tagSynonyms[canonicalName];
}
return canonicalName;
};
// initialize the default dictionary
dictionary = new Dictionary();
definitions.defineTags(dictionary);
// make the constructor available for unit-testing purposes
dictionary.Dictionary = Dictionary;
/** @type {module:jsdoc/tag/dictionary.Dictionary} */
module.exports = dictionary;
@@ -0,0 +1,900 @@
/**
Define tags that are known in JSDoc.
@module jsdoc/tag/dictionary/definitions
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var _ = require('underscore');
var jsdoc = {
name: require('jsdoc/name'),
src: {
astnode: require('jsdoc/src/astnode')
},
tag: {
type: require('jsdoc/tag/type')
},
util: {
doop: require('jsdoc/util/doop'),
logger: require('jsdoc/util/logger')
}
};
var path = require('jsdoc/path');
var Syntax = require('jsdoc/src/syntax').Syntax;
var hasOwnProp = Object.prototype.hasOwnProperty;
var DEFINITIONS = {
closure: 'closureTags',
jsdoc: 'jsdocTags'
};
var MODULE_NAMESPACE = 'module:';
// Clone a tag definition, excluding synonyms.
function cloneTagDef(tagDef, extras) {
var newTagDef = jsdoc.util.doop(tagDef);
delete newTagDef.synonyms;
return (extras ? _.extend(newTagDef, extras) : newTagDef);
}
function getSourcePaths() {
var sourcePaths = global.env.sourceFiles.slice(0) || [];
if (global.env.opts._) {
global.env.opts._.forEach(function(sourcePath) {
var resolved = path.resolve(global.env.pwd, sourcePath);
if (sourcePaths.indexOf(resolved) === -1) {
sourcePaths.push(resolved);
}
});
}
return sourcePaths;
}
function filepathMinusPrefix(filepath) {
var sourcePaths = getSourcePaths();
var commonPrefix = path.commonPrefix(sourcePaths);
var result = '';
if (filepath) {
filepath = path.normalize(filepath);
// always use forward slashes in the result
result = (filepath + path.sep).replace(commonPrefix, '')
.replace(/\\/g, '/');
}
if (result.length > 0 && result[result.length - 1] !== '/') {
result += '/';
}
return result;
}
/** @private */
function setDocletKindToTitle(doclet, tag) {
doclet.addTag( 'kind', tag.title );
}
function setDocletScopeToTitle(doclet, tag) {
try {
doclet.setScope(tag.title);
}
catch(e) {
jsdoc.util.logger.error(e.message);
}
}
function setDocletNameToValue(doclet, tag) {
if (tag.value && tag.value.description) { // as in a long tag
doclet.addTag('name', tag.value.description);
}
else if (tag.text) { // or a short tag
doclet.addTag('name', tag.text);
}
}
function setDocletNameToValueName(doclet, tag) {
if (tag.value && tag.value.name) {
doclet.addTag('name', tag.value.name);
}
}
function setDocletDescriptionToValue(doclet, tag) {
if (tag.value) {
doclet.addTag('description', tag.value);
}
}
function setDocletTypeToValueType(doclet, tag) {
if (tag.value && tag.value.type) {
// Add the type names and other type properties (such as `optional`).
// Don't overwrite existing properties.
Object.keys(tag.value).forEach(function(prop) {
if ( !hasOwnProp.call(doclet, prop) ) {
doclet[prop] = tag.value[prop];
}
});
}
}
function setNameToFile(doclet, tag) {
var name;
if (doclet.meta.filename) {
name = filepathMinusPrefix(doclet.meta.path) + doclet.meta.filename;
doclet.addTag('name', name);
}
}
function setDocletMemberof(doclet, tag) {
if (tag.value && tag.value !== '<global>') {
doclet.setMemberof(tag.value);
}
}
function applyNamespace(docletOrNs, tag) {
if (typeof docletOrNs === 'string') { // ns
tag.value = jsdoc.name.applyNamespace(tag.value, docletOrNs);
}
else { // doclet
if (!docletOrNs.name) {
return; // error?
}
docletOrNs.longname = jsdoc.name.applyNamespace(docletOrNs.name, tag.title);
}
}
function setDocletNameToFilename(doclet, tag) {
var name = '';
if (doclet.meta.path) {
name = filepathMinusPrefix(doclet.meta.path);
}
name += doclet.meta.filename.replace(/\.js$/i, '');
doclet.name = name;
}
function parseTypeText(text) {
var tagType = jsdoc.tag.type.parse(text, false, true);
return tagType.typeExpression || text;
}
function parseBorrows(doclet, tag) {
var m = /^(\S+)(?:\s+as\s+(\S+))?$/.exec(tag.text);
if (m) {
if (m[1] && m[2]) {
return { target: m[1], source: m[2] };
}
else if (m[1]) {
return { target: m[1] };
}
} else {
return {};
}
}
function stripModuleNamespace(name) {
return name.replace(/^module\:/, '');
}
function firstWordOf(string) {
var m = /^(\S+)/.exec(string);
if (m) { return m[1]; }
else { return ''; }
}
// Core JSDoc tags that are shared with other tag dictionaries.
var baseTags = exports.baseTags = {
abstract: {
mustNotHaveValue: true,
onTagged: function(doclet, tag) {
// we call this `virtual` because `abstract` is a reserved word
doclet.virtual = true;
},
synonyms: ['virtual']
},
access: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
// only valid values are private and protected, public is default
if ( /^(private|protected)$/i.test(tag.value) ) {
doclet.access = tag.value.toLowerCase();
}
else {
delete doclet.access;
}
}
},
alias: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.alias = tag.value;
}
},
// Special separator tag indicating that multiple doclets should be generated for the same
// comment. Used internally (and by some JSDoc users, although it's not officially supported).
// In the following example, the parser will replace `//**` with an `@also` tag:
// /**
// * Foo.
// *//**
// * Foo with a param.
// * @param {string} bar
// */
// function foo(bar) {}
also: {
onTagged: function(doclet, tag) {
// let the parser handle it; we define the tag here to avoid "not a known tag" errors
}
},
augments: {
mustHaveValue: true,
// Allow augments value to be specified as a normal type, e.g. {Type}
onTagText: parseTypeText,
onTagged: function(doclet, tag) {
doclet.augment( firstWordOf(tag.value) );
},
synonyms: ['extends']
},
author: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.author = doclet.author || [];
doclet.author.push(tag.value);
}
},
// this symbol has a member that should use the same docs as another symbol
borrows: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
var borrows = parseBorrows(doclet, tag);
doclet.borrow(borrows.target, borrows.source);
}
},
class: {
onTagged: function(doclet, tag) {
doclet.addTag('kind', 'class');
// We treat the @class tag as a @classdesc tag if all of the following are true:
// - Both @class and @constructor tags are present
// - There's no @classdesc tag
// - There are multiple words after @class
if (tag.value &&
tag.originalTitle === 'class' &&
/@construct(?:s|or)\b/i.test(doclet.comment) &&
!/@classdesc\b/i.test(doclet.comment) &&
tag.value.match(/\S+\s+\S+/)) {
doclet.addTag('classdesc', tag.value);
}
else {
setDocletNameToValue(doclet, tag);
}
},
synonyms: ['constructor']
},
classdesc: {
onTagged: function(doclet, tag) {
doclet.classdesc = tag.value;
}
},
constant: {
canHaveType: true,
canHaveName: true,
onTagged: function(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValueName(doclet, tag);
setDocletTypeToValueType(doclet, tag);
},
synonyms: ['const']
},
constructs: {
onTagged: function(doclet, tag) {
var ownerClassName;
if (!tag.value) {
// this can be resolved later in the handlers
ownerClassName = '{@thisClass}';
}
else {
ownerClassName = firstWordOf(tag.value);
}
doclet.addTag('alias', ownerClassName);
doclet.addTag('kind', 'class');
}
},
copyright: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.copyright = tag.value;
}
},
default: {
onTagged: function(doclet, tag) {
var type;
var value;
var nodeToString = jsdoc.src.astnode.nodeToString;
if (tag.value) {
doclet.defaultvalue = tag.value;
}
else if (doclet.meta && doclet.meta.code && doclet.meta.code.value) {
type = doclet.meta.code.type;
value = doclet.meta.code.value;
switch (type) {
case Syntax.ArrayExpression:
doclet.defaultvalue = nodeToString(doclet.meta.code.node);
doclet.defaultvaluetype = 'array';
break;
case Syntax.Literal:
doclet.defaultvalue = String(value);
break;
case Syntax.ObjectExpression:
doclet.defaultvalue = nodeToString(doclet.meta.code.node);
doclet.defaultvaluetype = 'object';
break;
default:
// do nothing
break;
}
}
},
synonyms: ['defaultvalue']
},
deprecated: {
// value is optional
onTagged: function(doclet, tag) {
doclet.deprecated = tag.value || true;
}
},
description: {
mustHaveValue: true,
synonyms: ['desc']
},
enum: {
canHaveType: true,
onTagged: function(doclet, tag) {
doclet.kind = 'member';
doclet.isEnum = true;
setDocletTypeToValueType(doclet, tag);
}
},
event: {
isNamespace: true,
onTagged: function(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValue(doclet, tag);
}
},
example: {
keepsWhitespace: true,
removesIndent: true,
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.examples = doclet.examples || [];
doclet.examples.push(tag.value);
}
},
exports: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
var modName = firstWordOf(tag.value);
// in case the user wrote something like `/** @exports module:foo */`:
doclet.addTag( 'alias', stripModuleNamespace(modName) );
doclet.addTag('kind', 'module');
}
},
external: {
canHaveType: true,
isNamespace: true,
onTagged: function(doclet, tag) {
setDocletKindToTitle(doclet, tag);
if (tag.value && tag.value.type) {
setDocletTypeToValueType(doclet, tag);
doclet.addTag('name', doclet.type.names[0]);
}
else {
setDocletNameToValue(doclet, tag);
}
},
synonyms: ['host']
},
file: {
onTagged: function(doclet, tag) {
setNameToFile(doclet, tag);
setDocletKindToTitle(doclet, tag);
setDocletDescriptionToValue(doclet, tag);
doclet.preserveName = true;
},
synonyms: ['fileoverview', 'overview']
},
fires: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.fires = doclet.fires || [];
applyNamespace('event', tag);
doclet.fires.push(tag.value);
},
synonyms: ['emits']
},
function: {
onTagged: function(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValue(doclet, tag);
},
synonyms: ['func', 'method']
},
global: {
mustNotHaveValue: true,
onTagged: function(doclet, tag) {
doclet.scope = jsdoc.name.SCOPE.NAMES.GLOBAL;
delete doclet.memberof;
}
},
ignore: {
mustNotHaveValue: true,
onTagged: function(doclet, tag) {
doclet.ignore = true;
}
},
implements: {
mustHaveValue: true,
onTagText: parseTypeText,
onTagged: function(doclet, tag) {
doclet.implements = doclet.implements || [];
doclet.implements.push(tag.value);
}
},
inheritdoc: {
mustNotHaveValue: true,
onTagged: function(doclet, tag) {
// use an empty string so JSDoc can support `@inheritdoc Foo#bar` in the future
doclet.inheritdoc = '';
}
},
inner: {
onTagged: function(doclet, tag) {
setDocletScopeToTitle(doclet, tag);
}
},
instance: {
onTagged: function(doclet, tag) {
setDocletScopeToTitle(doclet, tag);
}
},
interface: {
canHaveName: true,
onTagged: function(doclet, tag) {
doclet.addTag('kind', 'interface');
if (tag.value) {
setDocletNameToValueName(doclet, tag);
}
}
},
kind: {
mustHaveValue: true
},
lends: {
onTagged: function(doclet, tag) {
doclet.alias = tag.value || jsdoc.name.LONGNAMES.GLOBAL;
doclet.addTag('undocumented');
}
},
license: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.license = tag.value;
}
},
listens: {
mustHaveValue: true,
onTagged: function (doclet, tag) {
doclet.listens = doclet.listens || [];
applyNamespace('event', tag);
doclet.listens.push(tag.value);
}
},
member: {
canHaveType: true,
canHaveName: true,
onTagged: function(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValueName(doclet, tag);
setDocletTypeToValueType(doclet, tag);
},
synonyms: ['var']
},
memberof: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
if (tag.originalTitle === 'memberof!') {
doclet.forceMemberof = true;
if (tag.value === jsdoc.name.LONGNAMES.GLOBAL) {
doclet.addTag('global');
delete doclet.memberof;
}
}
setDocletMemberof(doclet, tag);
},
synonyms: ['memberof!']
},
// this symbol mixes in all of the specified object's members
mixes: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
var source = firstWordOf(tag.value);
doclet.mix(source);
}
},
mixin: {
onTagged: function(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValue(doclet, tag);
}
},
module: {
canHaveType: true,
isNamespace: true,
onTagged: function(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValue(doclet, tag);
if (!doclet.name) {
setDocletNameToFilename(doclet, tag);
}
// in case the user wrote something like `/** @module module:foo */`:
doclet.name = stripModuleNamespace(doclet.name);
setDocletTypeToValueType(doclet, tag);
}
},
name: {
mustHaveValue: true
},
namespace: {
canHaveType: true,
onTagged: function(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValue(doclet, tag);
setDocletTypeToValueType(doclet, tag);
}
},
param: {
canHaveType: true,
canHaveName: true,
onTagged: function(doclet, tag) {
doclet.params = doclet.params || [];
doclet.params.push(tag.value || {});
},
synonyms: ['arg', 'argument']
},
private: {
mustNotHaveValue: true,
onTagged: function(doclet, tag) {
doclet.access = 'private';
}
},
property: {
mustHaveValue: true,
canHaveType: true,
canHaveName: true,
onTagged: function(doclet, tag) {
doclet.properties = doclet.properties || [];
doclet.properties.push(tag.value);
},
synonyms: ['prop']
},
protected: {
mustNotHaveValue: true,
onTagged: function(doclet, tag) {
doclet.access = 'protected';
}
},
public: {
mustNotHaveValue: true,
onTagged: function(doclet, tag) {
delete doclet.access; // public is default
}
},
readonly: {
mustNotHaveValue: true,
onTagged: function(doclet, tag) {
doclet.readonly = true;
}
},
requires: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
var requiresName;
// inline link tags are passed through as-is so that `@requires {@link foo}` works
if ( require('jsdoc/tag/inline').isInlineTag(tag.value, 'link\\S*') ) {
requiresName = tag.value;
}
// otherwise, assume it's a module
else {
requiresName = firstWordOf(tag.value);
if (requiresName.indexOf(MODULE_NAMESPACE) !== 0) {
requiresName = MODULE_NAMESPACE + requiresName;
}
}
doclet.requires = doclet.requires || [];
doclet.requires.push(requiresName);
}
},
returns: {
mustHaveValue: true,
canHaveType: true,
onTagged: function(doclet, tag) {
doclet.returns = doclet.returns || [];
doclet.returns.push(tag.value);
},
synonyms: ['return']
},
see: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.see = doclet.see || [];
doclet.see.push(tag.value);
}
},
since: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.since = tag.value;
}
},
static: {
onTagged: function(doclet, tag) {
setDocletScopeToTitle(doclet, tag);
}
},
summary: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.summary = tag.value;
}
},
'this': {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet['this'] = firstWordOf(tag.value);
}
},
todo: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.todo = doclet.todo || [];
doclet.todo.push(tag.value);
}
},
platforms: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.platforms = doclet.platforms || [];
doclet.platforms = doclet.platforms.concat(tag.value.split(' '));
}
},
throws: {
mustHaveValue: true,
canHaveType: true,
onTagged: function(doclet, tag) {
doclet.exceptions = doclet.exceptions || [];
doclet.exceptions.push(tag.value);
},
synonyms: ['exception']
},
tutorial: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.tutorials = doclet.tutorials || [];
doclet.tutorials.push(tag.value);
}
},
type: {
mustHaveValue: true,
mustNotHaveDescription: true,
canHaveType: true,
onTagText: function(text) {
var closeIdx;
var openIdx;
var OPEN_BRACE = '{';
var CLOSE_BRACE = '}';
// remove line breaks
text = text.replace(/[\f\n\r]/g, '');
// Text must be a type expression; for backwards compatibility, we add braces if they're
// missing. But do NOT add braces to things like `@type {string} some pointless text`.
openIdx = text.indexOf(OPEN_BRACE);
closeIdx = text.indexOf(CLOSE_BRACE);
// a type expression is at least one character long
if ( openIdx !== 0 || closeIdx <= openIdx + 1) {
text = OPEN_BRACE + text + CLOSE_BRACE;
}
return text;
},
onTagged: function(doclet, tag) {
if (tag.value && tag.value.type) {
setDocletTypeToValueType(doclet, tag);
// for backwards compatibility, we allow @type for functions to imply return type
if (doclet.kind === 'function') {
doclet.addTag('returns', tag.text);
}
}
}
},
typedef: {
canHaveType: true,
canHaveName: true,
onTagged: function(doclet, tag) {
setDocletKindToTitle(doclet, tag);
if (tag.value) {
setDocletNameToValueName(doclet, tag);
// callbacks are always type {function}
if (tag.originalTitle === 'callback') {
doclet.type = {
names: [
'function'
]
};
}
else {
setDocletTypeToValueType(doclet, tag);
}
}
},
synonyms: ['callback']
},
undocumented: {
mustNotHaveValue: true,
onTagged: function(doclet, tag) {
doclet.undocumented = true;
doclet.comment = '';
}
},
variation: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.variation = tag.value;
}
},
version: {
mustHaveValue: true,
onTagged: function(doclet, tag) {
doclet.version = tag.value;
}
}
};
// Tag dictionary for JSDoc.
var jsdocTags = exports.jsdocTags = baseTags;
// Tag dictionary for Google Closure Compiler.
var closureTags = exports.closureTags = {
const: cloneTagDef(baseTags.constant),
constructor: cloneTagDef(baseTags.class),
deprecated: cloneTagDef(baseTags.deprecated),
enum: cloneTagDef(baseTags.enum),
extends: cloneTagDef(baseTags.augments),
final: cloneTagDef(baseTags.readonly),
implements: cloneTagDef(baseTags.implements),
inheritdoc: cloneTagDef(baseTags.inheritdoc),
interface: cloneTagDef(baseTags.interface, {
canHaveName: false,
mustNotHaveValue: true
}),
lends: cloneTagDef(baseTags.lends),
license: cloneTagDef(baseTags.license),
// Closure Compiler only
override: {
mustNotHaveValue: true,
onTagged: function(doclet, tag) {
doclet.override = true;
}
},
param: cloneTagDef(baseTags.param),
private: {
canHaveType: true,
onTagged: function(doclet, tag) {
doclet.access = 'private';
if (tag.value && tag.value.type) {
setDocletTypeToValueType(doclet, tag);
}
}
},
protected: {
canHaveType: true,
onTagged: function(doclet, tag) {
doclet.access = 'protected';
if (tag.value && tag.value.type) {
setDocletTypeToValueType(doclet, tag);
}
}
},
return: cloneTagDef(baseTags.returns),
'this': cloneTagDef(baseTags['this']),
throws: cloneTagDef(baseTags.throws),
type: cloneTagDef(baseTags.type, {
mustNotHaveDescription: false
}),
typedef: cloneTagDef(baseTags.typedef)
};
function addTagDefinitions(dictionary, tagDefs) {
Object.keys(tagDefs).forEach(function(tagName) {
var tagDef;
tagDef = tagDefs[tagName];
dictionary.defineTag(tagName, tagDef);
if (tagDef.synonyms) {
tagDef.synonyms.forEach(function(synonym) {
dictionary.defineSynonym(tagName, synonym);
});
}
});
}
/**
* Populate the given dictionary with the appropriate JSDoc tag definitions.
*
* If the `tagDefinitions` parameter is omitted, JSDoc uses its configuration settings to decide
* which tags to add to the dictionary.
*
* If the `tagDefinitions` parameter is included, JSDoc adds only the tag definitions from the
* `tagDefinitions` object. The configuration settings are ignored.
*
* @param {module:jsdoc/tag/dictionary} dictionary
* @param {Object} [tagDefinitions] - A dictionary whose values define the rules for a JSDoc tag.
*/
exports.defineTags = function(dictionary, tagDefinitions) {
var dictionaries;
if (!tagDefinitions) {
dictionaries = global.env.conf.tags.dictionaries;
if (!dictionaries) {
jsdoc.util.logger.error('The configuration setting "tags.dictionaries" is undefined. ' +
'Unable to load tag definitions.');
return;
}
else {
dictionaries = dictionaries.slice(0).reverse();
}
dictionaries.forEach(function(dictName) {
var tagDefs = exports[DEFINITIONS[dictName]];
if (!tagDefs) {
jsdoc.util.logger.error('The configuration setting "tags.dictionaries" contains ' +
'the unknown dictionary name %s. Ignoring the dictionary.', dictName);
return;
}
addTagDefinitions(dictionary, tagDefs);
});
}
else {
addTagDefinitions(dictionary, tagDefinitions);
}
};
@@ -0,0 +1,141 @@
/**
* @module jsdoc/tag/inline
*
* @author Jeff Williams <jeffrey.l.williams@gmail.com>
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
/**
* Information about an inline tag that was found within a string.
*
* @typedef {Object} InlineTagInfo
* @memberof module:jsdoc/tag/inline
* @property {?string} completeTag - The entire inline tag, including its enclosing braces.
* @property {?string} tag - The tag whose text was found.
* @property {?string} text - The tag text that was found.
*/
/**
* Information about the results of replacing inline tags within a string.
*
* @typedef {Object} InlineTagResult
* @memberof module:jsdoc/tag/inline
* @property {Array.<module:jsdoc/tag/inline.InlineTagInfo>} tags - The inline tags that were found.
* @property {string} newString - The updated text string after extracting or replacing the inline
* tags.
*/
/**
* Text-replacing function for strings that contain an inline tag.
*
* @callback InlineTagReplacer
* @memberof module:jsdoc/tag/inline
* @param {string} string - The complete string containing the inline tag.
* @param {module:jsdoc/tag/inline.InlineTagInfo} tagInfo - Information about the inline tag.
* @return {string} An updated version of the complete string.
*/
/**
* Create a regexp that matches a specific inline tag, or all inline tags.
*
* @private
* @memberof module:jsdoc/tag/inline
* @param {?string} tagName - The inline tag that the regexp will match. May contain regexp
* characters. If omitted, matches any string.
* @param {?string} prefix - A prefix for the regexp. Defaults to an empty string.
* @param {?string} suffix - A suffix for the regexp. Defaults to an empty string.
* @returns {RegExp} A regular expression that matches the requested inline tag.
*/
function regExpFactory(tagName, prefix, suffix) {
tagName = tagName || '\\S+';
prefix = prefix || '';
suffix = suffix || '';
return new RegExp(prefix + '\\{@' + tagName + '\\s+((?:.|\n)+?)\\}' + suffix, 'gi');
}
/**
* Check whether a string is an inline tag. You can check for a specific inline tag or for any valid
* inline tag.
*
* @param {string} string - The string to check.
* @param {?string} tagName - The inline tag to match. May contain regexp characters. If this
* parameter is omitted, this method returns `true` for any valid inline tag.
* @returns {boolean} Set to `true` if the string is a valid inline tag or `false` in all other
* cases.
*/
exports.isInlineTag = function(string, tagName) {
return regExpFactory(tagName, '^', '$').test(string);
};
/**
* Replace all instances of multiple inline tags with other text.
*
* @param {string} string - The string in which to replace the inline tags.
* @param {Object} replacers - The functions that are used to replace text in the string. The keys
* must contain tag names (for example, `link`), and the values must contain functions with the
* type {@link module:jsdoc/tag/inline.InlineTagReplacer}.
* @return {module:jsdoc/tag/inline.InlineTagResult} The updated string, as well as information
* about the inline tags that were found.
*/
exports.replaceInlineTags = function(string, replacers) {
var tagInfo = [];
function replaceMatch(replacer, tag, match, text) {
var matchedTag = {
completeTag: match,
tag: tag,
text: text
};
tagInfo.push(matchedTag);
return replacer(string, matchedTag);
}
string = string || '';
Object.keys(replacers).forEach(function(replacer) {
var tagRegExp = regExpFactory(replacer);
var matches;
// call the replacer once for each match
while ( (matches = tagRegExp.exec(string)) !== null ) {
string = replaceMatch(replacers[replacer], replacer, matches[0], matches[1]);
}
});
return {
tags: tagInfo,
newString: string.trim()
};
};
/**
* Replace all instances of an inline tag with other text.
*
* @param {string} string - The string in which to replace the inline tag.
* @param {string} tag - The name of the inline tag to replace.
* @param {module:jsdoc/tag/inline.InlineTagReplacer} replacer - The function that is used to
* replace text in the string.
* @return {module:jsdoc/tag/inline.InlineTagResult} The updated string, as well as information
* about the inline tags that were found.
*/
exports.replaceInlineTag = function(string, tag, replacer) {
var replacers = {};
replacers[tag] = replacer;
return exports.replaceInlineTags(string, replacers);
};
/**
* Extract inline tags from a string, replacing them with an empty string.
*
* @param {string} string - The string from which to extract text.
* @param {?string} tag - The inline tag to extract.
* @return {module:jsdoc/tag/inline.InlineTagResult} The updated string, as well as information
* about the inline tags that were found.
*/
exports.extractInlineTag = function(string, tag) {
return exports.replaceInlineTag(string, tag, function(str, tagInfo) {
return str.replace(tagInfo.completeTag, '');
});
};
@@ -0,0 +1,308 @@
/**
* @module jsdoc/tag/type
*
* @author Michael Mathews <micmath@gmail.com>
* @author Jeff Williams <jeffrey.l.williams@gmail.com>
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var catharsis = require('catharsis');
var jsdoc = {
name: require('jsdoc/name'),
tag: {
inline: require('jsdoc/tag/inline')
}
};
var util = require('util');
/**
* Information about a type expression extracted from tag text.
*
* @typedef TypeExpressionInfo
* @memberof module:jsdoc/tag/type
* @property {string} expression - The type expression.
* @property {string} text - The updated tag text.
*/
/** @private */
function unescapeBraces(text) {
return text.replace(/\\\{/g, '{')
.replace(/\\\}/g, '}');
}
/**
* Extract a type expression from the tag text.
*
* @private
* @param {string} string - The tag text.
* @return {module:jsdoc/tag/type.TypeExpressionInfo} The type expression and updated tag text.
*/
function extractTypeExpression(string) {
var completeExpression;
var count = 0;
var position = 0;
var expression = '';
var startIndex = string.search(/\{[^@]/);
var textStartIndex;
if (startIndex !== -1) {
// advance to the first character in the type expression
position = textStartIndex = startIndex + 1;
count++;
while (position < string.length) {
switch (string[position]) {
case '\\':
// backslash is an escape character, so skip the next character
position++;
break;
case '{':
count++;
break;
case '}':
count--;
break;
default:
// do nothing
}
if (count === 0) {
completeExpression = string.slice(startIndex, position + 1);
expression = string.slice(textStartIndex, position).trim();
break;
}
position++;
}
}
string = completeExpression ? string.replace(completeExpression, '') : string;
return {
expression: unescapeBraces(expression),
newString: string.trim()
};
}
/** @private */
function getTagInfo(tagValue, canHaveName, canHaveType) {
var name = '';
var typeExpression = '';
var text = tagValue;
var expressionAndText;
var nameAndDescription;
var typeOverride;
if (canHaveType) {
expressionAndText = extractTypeExpression(text);
typeExpression = expressionAndText.expression;
text = expressionAndText.newString;
}
if (canHaveName) {
nameAndDescription = jsdoc.name.splitName(text);
name = nameAndDescription.name;
text = nameAndDescription.description;
}
// an inline @type tag, like {@type Foo}, overrides the type expression
if (canHaveType) {
typeOverride = jsdoc.tag.inline.extractInlineTag(text, 'type');
if (typeOverride.tags && typeOverride.tags[0]) {
typeExpression = typeOverride.tags[0].text;
}
text = typeOverride.newString;
}
return {
name: name,
typeExpression: typeExpression,
text: text
};
}
/**
* Information provided in a JSDoc tag.
*
* @typedef {Object} TagInfo
* @memberof module:jsdoc/tag/type
* @property {string} TagInfo.defaultvalue - The default value of the member.
* @property {string} TagInfo.name - The name of the member (for example, `myParamName`).
* @property {boolean} TagInfo.nullable - Indicates whether the member can be set to `null` or
* `undefined`.
* @property {boolean} TagInfo.optional - Indicates whether the member is optional.
* @property {string} TagInfo.text - Descriptive text for the member (for example, `The user's email
* address.`).
* @property {Array.<string>} TagInfo.type - The type or types that the member can contain (for
* example, `string` or `MyNamespace.MyClass`).
* @property {string} TagInfo.typeExpression - The type expression that was parsed to identify the
* types.
* @property {boolean} TagInfo.variable - Indicates whether the number of members that are provided
* can vary (for example, in a function that accepts any number of parameters).
*/
// TODO: move to module:jsdoc/name?
/**
* Extract JSDoc-style type information from the name specified in the tag info, including the
* member name; whether the member is optional; and the default value of the member.
*
* @private
* @param {module:jsdoc/tag/type.TagInfo} tagInfo - Information contained in the tag.
* @return {module:jsdoc/tag/type.TagInfo} Updated information from the tag.
*/
function parseName(tagInfo) {
// like '[foo]' or '[ foo ]' or '[foo=bar]' or '[ foo=bar ]' or '[ foo = bar ]'
// or 'foo=bar' or 'foo = bar'
if ( /^(\[)?\s*(.+?)\s*(\])?$/.test(tagInfo.name) ) {
tagInfo.name = RegExp.$2;
// were the "optional" brackets present?
if (RegExp.$1 && RegExp.$3) {
tagInfo.optional = true;
}
// like 'foo=bar' or 'foo = bar'
if ( /^(.+?)\s*=\s*(.+)$/.test(tagInfo.name) ) {
tagInfo.name = RegExp.$1;
tagInfo.defaultvalue = RegExp.$2;
}
}
return tagInfo;
}
/** @private */
function getTypeStrings(parsedType, isOutermostType) {
var applications;
var typeString;
var types = [];
var TYPES = catharsis.Types;
switch (parsedType.type) {
case TYPES.AllLiteral:
types.push('*');
break;
case TYPES.FunctionType:
types.push('function');
break;
case TYPES.NameExpression:
types.push(parsedType.name);
break;
case TYPES.NullLiteral:
types.push('null');
break;
case TYPES.RecordType:
types.push('Object');
break;
case TYPES.TypeApplication:
// if this is the outermost type, we strip the modifiers; otherwise, we keep them
if (isOutermostType) {
applications = parsedType.applications.map(function(application) {
return catharsis.stringify(application);
}).join(', ');
typeString = util.format( '%s.<%s>', getTypeStrings(parsedType.expression),
applications );
types.push(typeString);
}
else {
types.push( catharsis.stringify(parsedType) );
}
break;
case TYPES.TypeUnion:
parsedType.elements.forEach(function(element) {
types = types.concat( getTypeStrings(element) );
});
break;
case TYPES.UndefinedLiteral:
types.push('undefined');
break;
case TYPES.UnknownLiteral:
types.push('?');
break;
default:
// this shouldn't happen
throw new Error( util.format('unrecognized type %s in parsed type: %j', parsedType.type,
parsedType) );
}
return types;
}
/**
* Extract JSDoc-style and Closure Compiler-style type information from the type expression
* specified in the tag info.
*
* @private
* @param {module:jsdoc/tag/type.TagInfo} tagInfo - Information contained in the tag.
* @return {module:jsdoc/tag/type.TagInfo} Updated information from the tag.
*/
function parseTypeExpression(tagInfo) {
var errorMessage;
var parsedType;
// don't try to parse empty type expressions
if (!tagInfo.typeExpression) {
return tagInfo;
}
try {
parsedType = catharsis.parse(tagInfo.typeExpression, {jsdoc: true});
}
catch (e) {
// always re-throw so the caller has a chance to report which file was bad
throw new Error( util.format('Invalid type expression "%s": %s', tagInfo.typeExpression,
e.message) );
}
tagInfo.type = tagInfo.type.concat( getTypeStrings(parsedType, true) );
tagInfo.parsedType = parsedType;
// Catharsis and JSDoc use the same names for 'optional' and 'nullable'...
['optional', 'nullable'].forEach(function(key) {
if (parsedType[key] !== null && parsedType[key] !== undefined) {
tagInfo[key] = parsedType[key];
}
});
// ...but not 'variable'.
if (parsedType.repeatable !== null && parsedType.repeatable !== undefined) {
tagInfo.variable = parsedType.repeatable;
}
return tagInfo;
}
// TODO: allow users to add/remove type parsers (perhaps via plugins)
var typeParsers = [parseName, parseTypeExpression];
/**
* Parse the value of a JSDoc tag.
*
* @param {string} tagValue - The value of the tag. For example, the tag `@param {string} name` has
* a value of `{string} name`.
* @param {boolean} canHaveName - Indicates whether the value can include a symbol name.
* @param {boolean} canHaveType - Indicates whether the value can include a type expression that
* describes the symbol.
* @return {module:jsdoc/tag/type.TagInfo} Information obtained from the tag.
* @throws {Error} Thrown if a type expression cannot be parsed.
*/
exports.parse = function(tagValue, canHaveName, canHaveType) {
if (typeof tagValue !== 'string') { tagValue = ''; }
var tagInfo = getTagInfo(tagValue, canHaveName, canHaveType);
tagInfo.type = tagInfo.type || [];
typeParsers.forEach(function(parser) {
tagInfo = parser.call(this, tagInfo);
});
// if we wanted a type, but the parsers didn't add any type names, use the type expression
if (canHaveType && !tagInfo.type.length && tagInfo.typeExpression) {
tagInfo.type = [tagInfo.typeExpression];
}
return tagInfo;
};
@@ -0,0 +1,56 @@
/*global env: true */
/**
@module jsdoc/tag/validator
@requires jsdoc/tag/dictionary
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var dictionary = require('jsdoc/tag/dictionary');
var format = require('util').format;
var logger = require('jsdoc/util/logger');
function buildMessage(tagName, meta, desc) {
var result = format('The @%s tag %s. File: %s, line: %s', tagName, desc, meta.filename,
meta.lineno);
if (meta.comment) {
result += '\n' + meta.comment;
}
return result;
}
/**
* Validate the given tag.
*/
exports.validate = function(tag, tagDef, meta) {
// handle cases where the tag definition does not exist
if (!tagDef) {
// log an error if unknown tags are not allowed
if (!env.conf.tags.allowUnknownTags) {
logger.error( buildMessage(tag.title, meta, 'is not a known tag') );
}
// stop validation, since there's nothing to validate against
return;
}
// check for errors that make the tag useless
if (!tagDef && !env.conf.tags.allowUnknownTags) {
logger.error( buildMessage(tag.title, meta, 'is not a known tag') );
}
else if (!tag.text && tagDef.mustHaveValue) {
logger.error( buildMessage(tag.title, meta, 'requires a value') );
}
// check for minor issues that are usually harmless
else if (tag.text && tagDef.mustNotHaveValue) {
logger.warn( buildMessage(tag.title, meta,
'does not permit a value; the value will be ignored') );
}
else if (tag.value && tag.value.description && tagDef.mustNotHaveDescription) {
logger.warn( buildMessage(tag.title, meta,
'does not permit a description; the description will be ignored') );
}
};
@@ -0,0 +1,83 @@
/**
* @file Wrapper for underscore's template utility to allow loading templates from files.
* @author Rafał Wrzeszcz <rafal.wrzeszcz@wrzasq.pl>
* @author <a href="mailto:matthewkastor@gmail.com">Matthew Christopher Kastor-Inare III</a>
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var _ = require('underscore'),
fs = require('jsdoc/fs'),
path = require('path');
/**
@module jsdoc/template
*/
/**
@class
@classdesc Underscore template helper.
@param {string} path - Templates directory.
*/
exports.Template = function(path) {
this.path = path;
this.layout = null;
this.cache = {};
// override default template tag settings
this.settings = {
evaluate: /<\?js([\s\S]+?)\?>/g,
interpolate: /<\?js=([\s\S]+?)\?>/g,
escape: /<\?js~([\s\S]+?)\?>/g
};
};
/** Loads template from given file.
@param {string} file - Template filename.
@return {function} Returns template closure.
*/
exports.Template.prototype.load = function(file) {
return _.template(fs.readFileSync(file, 'utf8'), null, this.settings);
};
/**
Renders template using given data.
This is low-level function, for rendering full templates use {@link Template.render()}.
@param {string} file - Template filename.
@param {object} data - Template variables (doesn't have to be object, but passing variables dictionary is best way and most common use).
@return {string} Rendered template.
*/
exports.Template.prototype.partial = function(file, data) {
file = path.resolve(this.path, file);
// load template into cache
if (!(file in this.cache)) {
this.cache[file] = this.load(file);
}
// keep template helper context
return this.cache[file].call(this, data);
};
/**
Renders template with given data.
This method automaticaly applies layout if set.
@param {string} file - Template filename.
@param {object} data - Template variables (doesn't have to be object, but passing variables dictionary is best way and most common use).
@return {string} Rendered template.
*/
exports.Template.prototype.render = function(file, data) {
// main content
var content = this.partial(file, data);
// apply layout
if (this.layout) {
data.content = content;
content = this.partial(this.layout, data);
}
return content;
};
@@ -0,0 +1,140 @@
/**
@overview
@author Rafał Wrzeszcz <rafal.wrzeszcz@wrzasq.pl>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var markdown = require('jsdoc/util/markdown');
var util = require('util');
var hasOwnProp = Object.prototype.hasOwnProperty;
/** Removes child tutorial from the parent. Does *not* unset child.parent though.
@param {Tutorial} parent - parent tutorial.
@param {Tutorial} child - Old child.
@private
*/
function removeChild(parent, child) {
var index = parent.children.indexOf(child);
if (index !== -1) {
parent.children.splice(index, 1);
}
}
/** Adds a child to the parent tutorial. Does *not* set child.parent though.
@param {Tutorial} parent - parent tutorial.
@param {Tutorial} child - New child.
@private
*/
function addChild(parent, child) {
parent.children.push(child);
}
/**
@module jsdoc/tutorial
*/
/**
@class
@classdesc Represents a single JSDoc tutorial.
@param {string} name - Tutorial name.
@param {string} content - Text content.
@param {number} type - Source formating.
*/
exports.Tutorial = function(name, content, type) {
this.title = this.name = name;
this.content = content;
this.type = type;
// default values
this.parent = null;
this.children = [];
};
/** Moves children from current parent to different one.
@param {?Tutorial} parent - New parent. If null, the tutorial has no parent.
*/
exports.Tutorial.prototype.setParent = function(parent) {
// removes node from old parent
if (this.parent) {
removeChild(this.parent, this);
}
this.parent = parent;
if (parent) {
addChild(parent, this);
}
};
/** Removes children from current node.
@param {Tutorial} child - Old child.
*/
exports.Tutorial.prototype.removeChild = function(child) {
child.setParent(null);
};
/** Adds new children to current node.
@param {Tutorial} child - New child.
*/
exports.Tutorial.prototype.addChild = function(child) {
child.setParent(this);
};
/** Prepares source.
@return {string} HTML source.
*/
exports.Tutorial.prototype.parse = function() {
switch (this.type) {
// nothing to do
case exports.TYPES.HTML:
return this.content;
// markdown
case exports.TYPES.MARKDOWN:
var mdParse = markdown.getParser();
return mdParse(this.content);
// uhm... should we react somehow?
// if not then this case can be merged with TYPES.HTML
default:
return this.content;
}
};
/**
* @class
* @classdesc Represents the root tutorial.
* @extends {module:jsdoc/tutorial.Tutorial}
*/
exports.RootTutorial = function() {
exports.RootTutorial.super_.call(this, '', '');
this._tutorials = {};
};
util.inherits(exports.RootTutorial, exports.Tutorial);
/**
* Retrieve a tutorial by name.
* @param {string} name - Tutorial name.
* @return {module:jsdoc/tutorial.Tutorial} Tutorial instance.
*/
exports.RootTutorial.prototype.getByName = function(name) {
return hasOwnProp.call(this._tutorials, name) && this._tutorials[name];
};
/**
* Add a child tutorial to the root.
* @param {module:jsdoc/tutorial.Tutorial} child - Child tutorial.
*/
exports.RootTutorial.prototype._addTutorial = function(child) {
this._tutorials[child.name] = child;
};
/** Tutorial source types.
@enum {number}
*/
exports.TYPES = {
HTML: 1,
MARKDOWN: 2
};
@@ -0,0 +1,192 @@
/**
@overview
@author Rafa&#322; Wrzeszcz <rafal.wrzeszcz@wrzasq.pl>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
/**
@module jsdoc/tutorial/resolver
*/
'use strict';
var logger = require('jsdoc/util/logger');
var fs = require('jsdoc/fs');
var path = require('path');
var tutorial = require('jsdoc/tutorial');
var hasOwnProp = Object.prototype.hasOwnProperty;
// TODO: make this an instance member of `RootTutorial`?
var conf = {};
var finder = /^(.*)\.(x(?:ht)?ml|html?|md|markdown|json)$/i;
/** checks if `conf` is the metadata for a single tutorial.
* A tutorial's metadata has a property 'title' and/or a property 'children'.
* @param {object} json - the object we want to test (typically from JSON.parse)
* @returns {boolean} whether `json` could be the metadata for a tutorial.
*/
function isTutorialJSON(json) {
// if conf.title exists or conf.children exists, it is metadata for a tutorial
return (hasOwnProp.call(json, 'title') || hasOwnProp.call(json, 'children'));
}
/**
* Root tutorial.
* @type {module:jsdoc/tutorial.Root}
*/
exports.root = new tutorial.RootTutorial();
/** Helper function that adds tutorial configuration to the `conf` variable.
* This helps when multiple tutorial configurations are specified in one object,
* or when a tutorial's children are specified as tutorial configurations as
* opposed to an array of tutorial names.
*
* Recurses as necessary to ensure all tutorials are added.
*
* @param {string} name - if `meta` is a configuration for a single tutorial,
* this is that tutorial's name.
* @param {object} meta - object that contains tutorial information.
* Can either be for a single tutorial, or for multiple
* (where each key in `meta` is the tutorial name and each
* value is the information for a single tutorial).
* Additionally, a tutorial's 'children' property may
* either be an array of strings (names of the child tutorials),
* OR an object giving the configuration for the child tutorials.
*/
function addTutorialConf(name, meta) {
var i;
var l;
var names;
if (isTutorialJSON(meta)) {
// if the children are themselves tutorial defintions as opposed to an
// array of strings, add each child.
if (hasOwnProp.call(meta, 'children') && !Array.isArray(meta.children)) {
names = Object.keys(meta.children);
for (i = 0, l = names.length; i < l; ++i) {
addTutorialConf(names[i], meta.children[names[i]]);
}
// replace with an array of names.
meta.children = names;
}
// check if the tutorial has already been defined...
if (hasOwnProp.call(conf, name)) {
logger.warn('Metadata for the tutorial %s is defined more than once. Only the first definition will be used.', name );
} else {
conf[name] = meta;
}
} else {
// keys are tutorial names, values are `Tutorial` instances
names = Object.keys(meta);
for (i = 0, l = names.length; i < l; ++i) {
addTutorialConf(names[i], meta[names[i]]);
}
}
}
/**
* Add a tutorial.
* @param {module:jsdoc/tutorial.Tutorial} current - Tutorial to add.
*/
exports.addTutorial = function(current) {
if (exports.root.getByName(current.name)) {
logger.warn('The tutorial %s is defined more than once. Only the first definition will be used.', current.name);
} else {
// by default, the root tutorial is the parent
current.setParent(exports.root);
exports.root._addTutorial(current);
}
};
/**
* Load tutorials from the given path.
* @param {string} filepath - Tutorials directory.
*/
exports.load = function(filepath) {
var content;
var current;
var files = fs.ls(filepath, global.env.opts.recurse ? 10 : undefined);
var name;
var match;
var type;
// tutorials handling
files.forEach(function(file) {
match = file.match(finder);
// any filetype that can apply to tutorials
if (match) {
name = path.basename(match[1]);
content = fs.readFileSync(file, global.env.opts.encoding);
switch (match[2].toLowerCase()) {
// HTML type
case 'xml':
case 'xhtml':
case 'html':
case 'htm':
type = tutorial.TYPES.HTML;
break;
// Markdown typs
case 'md':
case 'markdown':
type = tutorial.TYPES.MARKDOWN;
break;
// configuration file
case 'json':
var meta = JSON.parse(content);
addTutorialConf(name, meta);
// don't add this as a tutorial
return;
// how can it be? check `finder' regexp
default:
// not a file we want to work with
return;
}
current = new tutorial.Tutorial(name, content, type);
exports.addTutorial(current);
}
});
};
/** Resolves hierarchical structure.
*/
exports.resolve = function() {
var item;
var current;
Object.keys(conf).forEach(function(name) {
current = exports.root.getByName(name);
// TODO: should we complain about this?
if (!current) {
return;
}
item = conf[name];
// set title
if (item.title) {
current.title = item.title;
}
// add children
if (item.children) {
item.children.forEach(function(child) {
var childTutorial = exports.root.getByName(child);
if (!childTutorial) {
logger.error('Missing child tutorial: %s', child);
}
else {
childTutorial.setParent(current);
}
});
}
});
};
@@ -0,0 +1,83 @@
/*global Set */
/**
Deep clone a simple object. Ignores non-enumerable properties.
@private
*/
'use strict';
var setDefined = typeof Set !== 'undefined';
function addItem(set, item) {
if (setDefined) {
set.add(item);
}
else if (set.indexOf(item) === -1) {
set.push(item);
}
}
function hasItem(set, item) {
if (setDefined) {
return set.has(item);
}
else {
return set.indexOf(item) !== -1;
}
}
// TODO: can we remove the circular-ref checking? pretty sure it's not needed anymore...
// if we need this here for some reason I'm forgetting, we should share code with jsdoc/util/dumper
function doop(o, seen) {
var clone;
var descriptor;
var props;
var i;
var l;
if (!seen) {
seen = setDefined ? new Set() : [];
}
if (o instanceof Object && o.constructor !== Function) {
if ( hasItem(seen, o) ) {
clone = '<CircularRef>';
}
else {
addItem(seen, o);
if ( Array.isArray(o) ) {
clone = [];
for (i = 0, l = o.length; i < l; i++) {
clone[i] = (o[i] instanceof Object) ? doop(o[i], seen) : o[i];
}
}
else {
clone = Object.create( Object.getPrototypeOf(o) );
props = Object.keys(o);
for (i = 0, l = props.length; i < l; i++) {
descriptor = Object.getOwnPropertyDescriptor(o, props[i]);
if (descriptor.value) {
descriptor.value = doop(descriptor.value, seen);
}
Object.defineProperty(clone, props[i], descriptor);
}
}
}
return clone;
}
return o;
}
// Wrapper to avoid exposing the 'seen' parameter outside of this module.
function doopWrapper(o) {
return doop(o);
}
// for backwards compatibility
doopWrapper.doop = doopWrapper;
module.exports = doopWrapper;
@@ -0,0 +1,139 @@
/*global Set */
/**
* Recursively print out all names and values in a data structure.
* @module jsdoc/util/dumper
* @author Michael Mathews <micmath@gmail.com>
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var util = require('util');
var OBJECT_WALKER_KEY = 'hasBeenSeenByWalkerDumper';
var SET_DEFINED = (typeof Set !== 'undefined');
function ObjectWalker() {
this.seenItems = SET_DEFINED ? new Set() : [];
}
ObjectWalker.prototype.seen = function(object) {
var result;
if (SET_DEFINED) {
result = this.seenItems.has(object);
}
else {
result = object[OBJECT_WALKER_KEY];
}
return result;
};
ObjectWalker.prototype.markAsSeen = function(object) {
if (SET_DEFINED) {
this.seenItems.add(object);
}
else {
object[OBJECT_WALKER_KEY] = true;
this.seenItems.push(object);
}
};
ObjectWalker.prototype.removeSeenFlag = function(obj) {
if (SET_DEFINED) {
this.seenItems.delete(obj);
}
else {
delete obj[OBJECT_WALKER_KEY];
}
};
// some objects are unwalkable, like Java native objects
ObjectWalker.prototype.isUnwalkable = function(o) {
return (o && typeof o === 'object' && typeof o.constructor === 'undefined');
};
ObjectWalker.prototype.isFunction = function(o) {
return (o && typeof o === 'function' || o instanceof Function);
};
ObjectWalker.prototype.isObject = function(o) {
return o && o instanceof Object ||
(o && typeof o.constructor !== 'undefined' && o.constructor.name === 'Object');
};
ObjectWalker.prototype.checkCircularRefs = function(o, func) {
if ( this.seen(o) ) {
return '<CircularRef>';
}
else {
this.markAsSeen(o);
return func(o);
}
};
ObjectWalker.prototype.walk = function(o) {
var result;
var self = this;
if ( this.isUnwalkable(o) ) {
result = '<Object>';
}
else if ( o === undefined ) {
result = null;
}
else if ( Array.isArray(o) ) {
result = this.checkCircularRefs(o, function(arr) {
var newArray = [];
arr.forEach(function(item) {
newArray.push( self.walk(item) );
});
self.removeSeenFlag(arr);
return newArray;
});
}
else if ( util.isRegExp(o) ) {
result = '<RegExp ' + o + '>';
}
else if ( util.isDate(o) ) {
result = '<Date ' + o.toUTCString() + '>';
}
else if ( util.isError(o) ) {
result = { message: o.message };
}
else if ( this.isFunction(o) ) {
result = '<Function' + (o.name ? ' ' + o.name : '') + '>';
}
else if ( this.isObject(o) && o !== null ) {
result = this.checkCircularRefs(o, function(obj) {
var newObj = {};
Object.keys(obj).forEach(function(key) {
if (!SET_DEFINED && key === OBJECT_WALKER_KEY) { return; }
newObj[key] = self.walk(obj[key]);
});
self.removeSeenFlag(obj);
return newObj;
});
}
// should be safe to JSON.stringify() everything else
else {
result = o;
}
return result;
};
/**
* @param {*} object
*/
exports.dump = function(object) {
var walker = new ObjectWalker();
return JSON.stringify(walker.walk(object), null, 4);
};
@@ -0,0 +1,35 @@
/*global env: true */
/**
* Helper functions for handling errors.
*
* @deprecated As of JSDoc 3.3.0. This module may be removed in a future release. Use the module
* {@link module:jsdoc/util/logger} to log warnings and errors.
* @module jsdoc/util/error
*/
'use strict';
/**
* Log an exception as an error.
*
* Prior to JSDoc 3.3.0, this method would either log the exception (if lenient mode was enabled) or
* re-throw the exception (default).
*
* In JSDoc 3.3.0 and later, lenient mode has been replaced with strict mode, which is disabled by
* default. If strict mode is enabled, calling the `handle` method causes JSDoc to exit immediately,
* just as if the exception had been re-thrown.
*
* @deprecated As of JSDoc 3.3.0. This module may be removed in a future release.
* @param {Error} e - The exception to log.
* @memberof module:jsdoc/util/error
*/
exports.handle = function(e) {
var logger = require('jsdoc/util/logger');
var msg = e ? ( e.message || JSON.stringify(e) ) : '';
// include the error type if it's an Error object
if (e instanceof Error) {
msg = e.name + ': ' + msg;
}
logger.error(msg);
};
@@ -0,0 +1,232 @@
/**
* Logging tools for JSDoc.
*
* Log messages are printed to the console based on the current logging level. By default, messages
* at level `{@link module:jsdoc/util/logger.LEVELS.ERROR}` or above are logged; all other messages
* are ignored.
*
* In addition, the module object emits an event whenever a logger method is called, regardless of
* the current logging level. The event's name is the string `logger:` followed by the logger's name
* (for example, `logger:error`). The event handler receives an array of arguments that were passed
* to the logger method.
*
* Each logger method accepts a `message` parameter that may contain zero or more placeholders. Each
* placeholder is replaced by the corresponding argument following the message. If the placeholder
* does not have a corresponding argument, the placeholder is not replaced.
*
* The following placeholders are supported:
*
* + `%s`: String.
* + `%d`: Number.
* + `%j`: JSON.
*
* @module jsdoc/util/logger
* @extends module:events.EventEmitter
* @example
* var logger = require('jsdoc/util/logger');
*
* var data = {
* foo: 'bar'
* };
* var name = 'baz';
*
* logger.warn('%j %s', data, name); // prints '{"foo":"bar"} baz'
* @see http://nodejs.org/api/util.html#util_util_format_format
*/
'use strict';
var runtime = require('jsdoc/util/runtime');
var util = require('util');
function Logger() {}
util.inherits(Logger, require('events').EventEmitter);
var logger = module.exports = new Logger();
/**
* Logging levels for the JSDoc logger. The default logging level is
* {@link module:jsdoc/util/logger.LEVELS.ERROR}.
*
* @enum
* @type {number}
*/
var LEVELS = logger.LEVELS = {
/** Do not log any messages. */
SILENT: 0,
/** Log fatal errors that prevent JSDoc from running. */
FATAL: 10,
/** Log all errors, including errors from which JSDoc can recover. */
ERROR: 20,
/**
* Log the following messages:
*
* + Warnings
* + Errors
*/
WARN: 30,
/**
* Log the following messages:
*
* + Informational messages
* + Warnings
* + Errors
*/
INFO: 40,
/**
* Log the following messages:
*
* + Debugging messages
* + Informational messages
* + Warnings
* + Errors
*/
DEBUG: 50,
/** Log all messages. */
VERBOSE: 1000
};
var DEFAULT_LEVEL = LEVELS.WARN;
var logLevel = DEFAULT_LEVEL;
var PREFIXES = {
DEBUG: 'DEBUG: ',
ERROR: 'ERROR: ',
FATAL: 'FATAL: ',
WARN: 'WARNING: '
};
// Add a prefix to a log message if necessary.
function addPrefix(args, prefix) {
var updatedArgs;
if (prefix && typeof args[0] === 'string') {
updatedArgs = args.slice(0);
updatedArgs[0] = prefix + updatedArgs[0];
}
return updatedArgs || args;
}
// TODO: document events
function wrapLogFunction(name, func) {
var eventName = 'logger:' + name;
var upperCaseName = name.toUpperCase();
var level = LEVELS[upperCaseName];
var prefix = PREFIXES[upperCaseName];
return function() {
var loggerArgs;
var args = Array.prototype.slice.call(arguments, 0);
if (logLevel >= level) {
loggerArgs = addPrefix(args, prefix);
func.apply(null, loggerArgs);
}
args.unshift(eventName);
logger.emit.apply(logger, args);
};
}
// Print a message to STDOUT without a terminating newline.
function printToStdout() {
var args = Array.prototype.slice.call(arguments, 0);
process.stdout.write( util.format.apply(util, args) );
}
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.DEBUG}.
*
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.debug = wrapLogFunction('debug', console.info);
/**
* Print a string at log level {@link module:jsdoc/util/logger.LEVELS.DEBUG}. The string is not
* terminated by a newline.
*
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.printDebug = wrapLogFunction('debug', printToStdout);
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.ERROR}.
*
* @name module:jsdoc/util/logger.error
* @function
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.error = wrapLogFunction('error', console.error);
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.FATAL}.
*
* @name module:jsdoc/util/logger.fatal
* @function
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.fatal = wrapLogFunction('fatal', console.error);
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.INFO}.
*
* @name module:jsdoc/util/logger.info
* @function
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.info = wrapLogFunction('info', console.info);
/**
* Print a string at log level {@link module:jsdoc/util/logger.LEVELS.INFO}. The string is not
* terminated by a newline.
*
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.printInfo = wrapLogFunction('info', printToStdout);
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.VERBOSE}.
*
* @name module:jsdoc/util/logger.verbose
* @function
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.verbose = wrapLogFunction('verbose', console.info);
/**
* Print a string at log level {@link module:jsdoc/util/logger.LEVELS.VERBOSE}. The string is not
* terminated by a newline.
*
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.printVerbose = wrapLogFunction('verbose', printToStdout);
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.WARN}.
*
* @name module:jsdoc/util/logger.warn
* @function
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.warn = wrapLogFunction('warn', console.warn);
/**
* Set the log level.
*
* @param {module:jsdoc/util/logger.LEVELS} level - The log level to use.
*/
logger.setLevel = function setLevel(level) {
logLevel = (level !== undefined) ? level : DEFAULT_LEVEL;
};
/**
* Get the current log level.
*
* @return {module:jsdoc/util/logger.LEVELS} The current log level.
*/
logger.getLevel = function getLevel() {
return logLevel;
};
@@ -0,0 +1,156 @@
/*global env */
/**
* Provides access to Markdown-related functions.
* @module jsdoc/util/markdown
* @author Michael Mathews <micmath@gmail.com>
* @author Ben Blank <ben.blank@gmail.com>
*/
'use strict';
var util = require('util');
/**
* Enumeration of Markdown parsers that are available.
* @enum {String}
*/
var parserNames = {
/**
* The "[markdown-js](https://github.com/evilstreak/markdown-js)" (aka "evilstreak") parser.
*
* @deprecated Replaced by "marked," as markdown-js does not support inline HTML.
*/
evilstreak: 'marked',
/**
* The "GitHub-flavored Markdown" parser.
* @deprecated Replaced by "marked."
*/
gfm: 'marked',
/**
* The "[Marked](https://github.com/chjj/marked)" parser.
*/
marked: 'marked'
};
/**
* Escape underscores that occur within {@ ... } in order to protect them
* from the markdown parser(s).
* @param {String} source the source text to sanitize.
* @returns {String} `source` where underscores within {@ ... } have been
* protected with a preceding backslash (i.e. \_) -- the markdown parsers
* will strip the backslash and protect the underscore.
*/
function escapeUnderscores(source) {
return source.replace(/\{@[^}\r\n]+\}/g, function (wholeMatch) {
return wholeMatch.replace(/(^|[^\\])_/g, '$1\\_');
});
}
/**
* Escape HTTP/HTTPS URLs so that they are not automatically converted to HTML links.
*
* @param {string} source - The source text to escape.
* @return {string} The source text with escape characters added to HTTP/HTTPS URLs.
*/
function escapeUrls(source) {
return source.replace(/(https?)\:\/\//g, '$1:\\/\\/');
}
/**
* Unescape HTTP/HTTPS URLs after Markdown parsing is complete.
*
* @param {string} source - The source text to unescape.
* @return {string} The source text with escape characters removed from HTTP/HTTPS URLs.
*/
function unescapeUrls(source) {
return source.replace(/(https?)\:\\\/\\\//g, '$1://');
}
/**
* Escape characters in text within a code block.
*
* @param {string} source - The source text to escape.
* @return {string} The escaped source text.
*/
function escapeCode(source) {
return source.replace(/</g, '&lt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
/**
* Retrieve a function that accepts a single parameter containing Markdown source. The function uses
* the specified parser to transform the Markdown source to HTML, then returns the HTML as a string.
*
* @private
* @param {String} parserName The name of the selected parser.
* @param {Object} [conf] Configuration for the selected parser, if any.
* @returns {Function} A function that accepts Markdown source, feeds it to the selected parser, and
* returns the resulting HTML.
*/
function getParseFunction(parserName, conf) {
var logger = require('jsdoc/util/logger');
var marked = require('marked');
var markedRenderer;
var parserFunction;
conf = conf || {};
if (parserName === parserNames.marked) {
// Marked generates an "id" attribute for headers; this custom renderer suppresses it
markedRenderer = new marked.Renderer();
markedRenderer.heading = function(text, level) {
return util.format('<h%s>%s</h%s>', level, text, level);
};
// Allow prettyprint to work on inline code samples
markedRenderer.code = function(code, language) {
var langClass = language ? ' lang-' + language : '';
return util.format( '<pre class="prettyprint source%s"><code>%s</code></pre>',
langClass, escapeCode(code) );
};
parserFunction = function(source) {
var result;
source = escapeUnderscores(source);
source = escapeUrls(source);
result = marked(source, { renderer: markedRenderer })
.replace(/\s+$/, '')
.replace(/&#39;/g, "'");
result = unescapeUrls(result);
return result;
};
parserFunction._parser = parserNames.marked;
return parserFunction;
}
else {
logger.error('Unrecognized Markdown parser "%s". Markdown support is disabled.',
parserName);
}
}
/**
* Retrieve a Markdown parsing function based on the value of the `conf.json` file's
* `env.conf.markdown` property. The parsing function accepts a single parameter containing Markdown
* source. The function uses the parser specified in `conf.json` to transform the Markdown source to
* HTML, then returns the HTML as a string.
*
* @returns {function} A function that accepts Markdown source, feeds it to the selected parser, and
* returns the resulting HTML.
*/
exports.getParser = function() {
var conf = env.conf.markdown;
if (conf && conf.parser) {
return getParseFunction(parserNames[conf.parser], conf);
}
else {
// marked is the default parser
return getParseFunction(parserNames.marked, conf);
}
};
@@ -0,0 +1,117 @@
/*global env, java */
/**
* Helper functions to enable JSDoc to run on multiple JavaScript runtimes.
*
* @module jsdoc/util/runtime
* @private
*/
'use strict';
var os = require('os');
// These strings represent directory names; do not modify them!
/** @private */
var RHINO = exports.RHINO = 'rhino';
/** @private */
var NODE = exports.NODE = 'node';
/**
* The JavaScript runtime that is executing JSDoc:
*
* + `module:jsdoc/util/runtime~RHINO`: Mozilla Rhino.
* + `module:jsdoc/util/runtime~NODE`: Node.js.
*
* @private
*/
var runtime = (function() {
if (global.Packages && typeof global.Packages === 'object' &&
Object.prototype.toString.call(global.Packages) === '[object JavaPackage]') {
return RHINO;
} else if (require && require.main && module) {
return NODE;
} else {
// unknown runtime
throw new Error('Unable to identify the current JavaScript runtime.');
}
})();
/**
* Check whether Mozilla Rhino is running JSDoc.
* @return {boolean} Set to `true` if the current runtime is Mozilla Rhino.
*/
exports.isRhino = function() {
return runtime === RHINO;
};
/**
* Check whether Node.js is running JSDoc.
* @return {boolean} Set to `true` if the current runtime is Node.js.
*/
exports.isNode = function() {
return runtime === NODE;
};
function initializeRhino(args) {
// the JSDoc dirname is the main module URI, minus the filename, converted to a path
var uriParts = require.main.uri.split('/');
uriParts.pop();
env.dirname = String( new java.io.File(new java.net.URI(uriParts.join('/'))) );
env.pwd = String( java.lang.System.getenv().get('PWD') );
env.args = args;
require(env.dirname + '/rhino/rhino-shim.js');
}
function initializeNode(args) {
var fs = require('fs');
var path = require('path');
var jsdocPath = args[0];
var pwd = args[1];
// resolve the path if it's a symlink
if ( fs.statSync(jsdocPath).isSymbolicLink() ) {
jsdocPath = path.resolve( path.dirname(jsdocPath), fs.readlinkSync(jsdocPath) );
}
env.dirname = jsdocPath;
env.pwd = pwd;
env.args = process.argv.slice(2);
}
exports.initialize = function(args) {
switch (runtime) {
case RHINO:
initializeRhino(args);
break;
case NODE:
initializeNode(args);
break;
default:
throw new Error('Cannot initialize the unknown JavaScript runtime "' + runtime + '"!');
}
};
/**
* Retrieve the identifier for the current JavaScript runtime.
*
* @private
* @return {string} The runtime identifier.
*/
exports.getRuntime = function() {
return runtime;
};
/**
* Get the require path for the runtime-specific implementation of a module.
*
* @param {string} partialPath - The partial path to the module. Use the same format as when calling
* `require()`.
* @return {object} The require path for the runtime-specific implementation of the module.
*/
exports.getModulePath = function(partialPath) {
var path = require('path');
return path.join(env.dirname, runtime, partialPath);
};
@@ -0,0 +1,920 @@
/*global env: true */
/**
* @module jsdoc/util/templateHelper
*/
'use strict';
var catharsis = require('catharsis');
var dictionary = require('jsdoc/tag/dictionary');
var name = require('jsdoc/name');
var util = require('util');
var hasOwnProp = Object.prototype.hasOwnProperty;
var MODULE_NAMESPACE = 'module:';
var files = {};
var ids = {};
// each container gets its own html file
var containers = ['class', 'module', 'external', 'namespace', 'mixin', 'interface'];
var tutorials;
/** Sets tutorials map.
@param {jsdoc.tutorial.Tutorial} root - Root tutorial node.
*/
exports.setTutorials = function(root) {
tutorials = root;
};
exports.globalName = name.SCOPE.NAMES.GLOBAL;
exports.fileExtension = '.html';
exports.scopeToPunc = name.scopeToPunc;
var linkMap = {
// two-way lookup
longnameToUrl: {},
urlToLongname: {},
// one-way lookup (IDs are only unique per file)
longnameToId: {}
};
// two-way lookup
var tutorialLinkMap = {
nameToUrl: {},
urlToName: {}
};
var longnameToUrl = exports.longnameToUrl = linkMap.longnameToUrl;
var longnameToId = exports.longnameToId = linkMap.longnameToId;
var registerLink = exports.registerLink = function(longname, fileUrl) {
linkMap.longnameToUrl[longname] = fileUrl;
linkMap.urlToLongname[fileUrl] = longname;
};
var registerId = exports.registerId = function(longname, fragment) {
linkMap.longnameToId[longname] = fragment;
};
function getNamespace(kind) {
if (dictionary.isNamespace(kind)) {
return kind + ':';
}
return '';
}
function formatNameForLink(doclet, options) {
var newName = getNamespace(doclet.kind) + (doclet.name || '') + (doclet.variation || '');
var scopePunc = exports.scopeToPunc[doclet.scope] || '';
// Only prepend the scope punctuation if it's not the same character that marks the start of a
// fragment ID. Using `#` in HTML5 fragment IDs is legal, but URLs like `foo.html##bar` are
// just confusing.
if (scopePunc !== '#') {
newName = scopePunc + newName;
}
return newName;
}
function makeUniqueFilename(filename, str) {
var key = filename.toLowerCase();
var nonUnique = true;
// don't allow filenames to begin with an underscore
if (!filename.length || filename[0] === '_') {
filename = '-' + filename;
key = filename.toLowerCase();
}
// append enough underscores to make the filename unique
while (nonUnique) {
if ( hasOwnProp.call(files, key) ) {
filename += '_';
key = filename.toLowerCase();
} else {
nonUnique = false;
}
}
files[key] = str;
return filename;
}
/**
* Convert a string to a unique filename, including an extension.
*
* Filenames are cached to ensure that they are used only once. For example, if the same string is
* passed in twice, two different filenames will be returned.
*
* Also, filenames are not considered unique if they are capitalized differently but are otherwise
* identical.
* @param {string} str The string to convert.
* @return {string} The filename to use for the string.
*/
var getUniqueFilename = exports.getUniqueFilename = function(str) {
var namespaces = dictionary.getNamespaces().join('|');
var basename = (str || '')
// use - instead of : in namespace prefixes
.replace(new RegExp('^(' + namespaces + '):'), '$1-')
// replace characters that can cause problems on some filesystems
.replace(/[\\\/?*:|'"<>]/g, '_')
// use - instead of ~ to denote 'inner'
.replace(/~/g, '-')
// use _ instead of # to denote 'instance'
.replace(/\#/g, '_')
// use _ instead of / (for example, in module names)
.replace(/\//g, '_')
// remove the variation, if any
.replace(/\([\s\S]*\)$/, '')
// make sure we don't create hidden files, or files whose names start with a dash
.replace(/^[\.\-]/, '');
// in case we've now stripped the entire basename (uncommon, but possible):
basename = basename.length ? basename : '_';
return makeUniqueFilename(basename, str) + exports.fileExtension;
};
/**
* Get a longname's filename if one has been registered; otherwise, generate a unique filename, then
* register the filename.
* @private
*/
function getFilename(longname) {
var fileUrl;
if ( hasOwnProp.call(longnameToUrl, longname) ) {
fileUrl = longnameToUrl[longname];
}
else {
fileUrl = getUniqueFilename(longname);
registerLink(longname, fileUrl);
}
return fileUrl;
}
/**
* Check whether a symbol is the only symbol exported by a module (as in
* `module.exports = function() {};`).
*
* @private
* @param {module:jsdoc/doclet.Doclet} doclet - The doclet for the symbol.
* @return {boolean} `true` if the symbol is the only symbol exported by a module; otherwise,
* `false`.
*/
function isModuleExports(doclet) {
return doclet.longname && doclet.longname === doclet.name &&
doclet.longname.indexOf(MODULE_NAMESPACE) === 0 && doclet.kind !== 'module';
}
function makeUniqueId(filename, id) {
var key;
var nonUnique = true;
key = id.toLowerCase();
// HTML5 IDs cannot contain whitespace characters
id = id.replace(/\s/g, '');
// append enough underscores to make the identifier unique
while (nonUnique) {
if ( hasOwnProp.call(ids, filename) && hasOwnProp.call(ids[filename], key) ) {
id += '_';
key = id.toLowerCase();
}
else {
nonUnique = false;
}
}
ids[filename] = ids[filename] || {};
ids[filename][key] = id;
return id;
}
/**
* Get a doclet's ID if one has been registered; otherwise, generate a unique ID, then register
* the ID.
* @private
*/
function getId(longname, id) {
if ( hasOwnProp.call(longnameToId, longname) ) {
id = longnameToId[longname];
}
else if (!id) {
// no ID required
return '';
}
else {
id = makeUniqueId(longname, id);
registerId(longname, id);
}
return id;
}
/**
* Convert a doclet to an identifier that is unique for a specified filename.
*
* Identifiers are not considered unique if they are capitalized differently but are otherwise
* identical.
*
* @method
* @param {string} filename - The file in which the identifier will be used.
* @param {string} doclet - The doclet to convert.
* @return {string} A unique identifier based on the file and doclet.
*/
var getUniqueId = exports.getUniqueId = makeUniqueId;
var htmlsafe = exports.htmlsafe = function(str) {
return str.replace(/&/g, '&amp;')
.replace(/</g, '&lt;');
};
function parseType(longname) {
var err;
try {
return catharsis.parse(longname, {jsdoc: true});
}
catch (e) {
err = new Error('unable to parse ' + longname + ': ' + e.message);
require('jsdoc/util/logger').error(err);
return longname;
}
}
function stringifyType(parsedType, cssClass, linkMap) {
return require('catharsis').stringify(parsedType, {
cssClass: cssClass,
htmlSafe: true,
links: linkMap
});
}
function hasUrlPrefix(text) {
return (/^(http|ftp)s?:\/\//).test(text);
}
function isComplexTypeExpression(expr) {
// record types, type unions, and type applications all count as "complex"
return expr.search(/[{(|]/) !== -1 || expr.search(/</) > 0;
}
function fragmentHash(fragmentId) {
if (!fragmentId) {
return '';
}
return '#' + fragmentId;
}
/**
* Build an HTML link to the symbol with the specified longname. If the longname is not
* associated with a URL, this method simply returns the link text, if provided, or the longname.
*
* The `longname` parameter can also contain a URL rather than a symbol's longname.
*
* This method supports type applications that can contain one or more types, such as
* `Array.<MyClass>` or `Array.<(MyClass|YourClass)>`. In these examples, the method attempts to
* replace `Array`, `MyClass`, and `YourClass` with links to the appropriate types. The link text
* is ignored for type applications.
*
* @param {string} longname - The longname (or URL) that is the target of the link.
* @param {string=} linkText - The text to display for the link, or `longname` if no text is
* provided.
* @param {Object} options - Options for building the link.
* @param {string=} options.cssClass - The CSS class (or classes) to include in the link's `<a>`
* tag.
* @param {string=} options.fragmentId - The fragment identifier (for example, `name` in
* `foo.html#name`) to append to the link target.
* @param {string=} options.linkMap - The link map in which to look up the longname.
* @param {boolean=} options.monospace - Indicates whether to display the link text in a monospace
* font.
* @return {string} The HTML link, or the link text if the link is not available.
*/
function buildLink(longname, linkText, options) {
var classString = options.cssClass ? util.format(' class="%s"', options.cssClass) : '';
var fileUrl;
var fragmentString = fragmentHash(options.fragmentId);
var stripped;
var text;
var parsedType;
// handle cases like:
// @see <http://example.org>
// @see http://example.org
stripped = longname ? longname.replace(/^<|>$/g, '') : '';
if ( hasUrlPrefix(stripped) ) {
fileUrl = stripped;
text = linkText || stripped;
}
// handle complex type expressions that may require multiple links
// (but skip anything that looks like an inline tag)
else if (longname && isComplexTypeExpression(longname) && /\{\@.+\}/.test(longname) === false) {
parsedType = parseType(longname);
return stringifyType(parsedType, options.cssClass, options.linkMap);
}
else {
fileUrl = hasOwnProp.call(options.linkMap, longname) ? options.linkMap[longname] : '';
text = linkText || longname;
}
text = options.monospace ? '<code>' + text + '</code>' : text;
if (!fileUrl) {
return text;
}
else {
return util.format('<a href="%s"%s>%s</a>', encodeURI(fileUrl + fragmentString),
classString, text);
}
}
/**
* Retrieve an HTML link to the symbol with the specified longname. If the longname is not
* associated with a URL, this method simply returns the link text, if provided, or the longname.
*
* The `longname` parameter can also contain a URL rather than a symbol's longname.
*
* This method supports type applications that can contain one or more types, such as
* `Array.<MyClass>` or `Array.<(MyClass|YourClass)>`. In these examples, the method attempts to
* replace `Array`, `MyClass`, and `YourClass` with links to the appropriate types. The link text
* is ignored for type applications.
*
* @param {string} longname - The longname (or URL) that is the target of the link.
* @param {string=} linkText - The text to display for the link, or `longname` if no text is
* provided.
* @param {string=} cssClass - The CSS class (or classes) to include in the link's `<a>` tag.
* @param {string=} fragmentId - The fragment identifier (for example, `name` in `foo.html#name`) to
* append to the link target.
* @return {string} The HTML link, or a plain-text string if the link is not available.
*/
var linkto = exports.linkto = function(longname, linkText, cssClass, fragmentId) {
return buildLink(longname, linkText, {
cssClass: cssClass,
fragmentId: fragmentId,
linkMap: longnameToUrl
});
};
function useMonospace(tag, text) {
var cleverLinks;
var monospaceLinks;
var result;
if ( hasUrlPrefix(text) ) {
result = false;
}
else if (tag === 'linkplain') {
result = false;
}
else if (tag === 'linkcode') {
result = true;
}
else {
cleverLinks = env.conf.templates.cleverLinks;
monospaceLinks = env.conf.templates.monospaceLinks;
if (monospaceLinks || cleverLinks) {
result = true;
}
}
return result || false;
}
function splitLinkText(text) {
var linkText;
var target;
var splitIndex;
// if a pipe is not present, we split on the first space
splitIndex = text.indexOf('|');
if (splitIndex === -1) {
splitIndex = text.search(/\s/);
}
if (splitIndex !== -1) {
linkText = text.substr(splitIndex + 1);
// Normalize subsequent newlines to a single space.
linkText = linkText.replace(/\n+/, ' ');
target = text.substr(0, splitIndex);
}
return {
linkText: linkText,
target: target || text
};
}
var tutorialToUrl = exports.tutorialToUrl = function(tutorial) {
var fileUrl;
var node = tutorials.getByName(tutorial);
// no such tutorial
if (!node) {
require('jsdoc/util/logger').error( new Error('No such tutorial: ' + tutorial) );
return null;
}
// define the URL if necessary
if (!hasOwnProp.call(tutorialLinkMap.nameToUrl, node.name)) {
fileUrl = 'tutorial-' + getUniqueFilename(node.name);
tutorialLinkMap.nameToUrl[node.name] = fileUrl;
tutorialLinkMap.urlToName[fileUrl] = node.name;
}
return tutorialLinkMap.nameToUrl[node.name];
};
/**
* Retrieve a link to a tutorial, or the name of the tutorial if the tutorial is missing. If the
* `missingOpts` parameter is supplied, the names of missing tutorials will be prefixed by the
* specified text and wrapped in the specified HTML tag and CSS class.
*
* @todo Deprecate missingOpts once we have a better error-reporting mechanism.
* @param {string} tutorial The name of the tutorial.
* @param {string} content The link text to use.
* @param {object} [missingOpts] Options for displaying the name of a missing tutorial.
* @param {string} missingOpts.classname The CSS class to wrap around the tutorial name.
* @param {string} missingOpts.prefix The prefix to add to the tutorial name.
* @param {string} missingOpts.tag The tag to wrap around the tutorial name.
* @return {string} An HTML link to the tutorial, or the name of the tutorial with the specified
* options.
*/
var toTutorial = exports.toTutorial = function(tutorial, content, missingOpts) {
if (!tutorial) {
require('jsdoc/util/logger').error( new Error('Missing required parameter: tutorial') );
return null;
}
var node = tutorials.getByName(tutorial);
// no such tutorial
if (!node) {
missingOpts = missingOpts || {};
var tag = missingOpts.tag;
var classname = missingOpts.classname;
var link = tutorial;
if (missingOpts.prefix) {
link = missingOpts.prefix + link;
}
if (tag) {
link = '<' + tag + (classname ? (' class="' + classname + '">') : '>') + link;
link += '</' + tag + '>';
}
return link;
}
content = content || node.title;
return '<a href="' + tutorialToUrl(tutorial) + '">' + content + '</a>';
};
/** Find symbol {@link ...} and {@tutorial ...} strings in text and turn into html links */
exports.resolveLinks = function(str) {
var replaceInlineTags = require('jsdoc/tag/inline').replaceInlineTags;
function extractLeadingText(string, completeTag) {
var tagIndex = string.indexOf(completeTag);
var leadingText = null;
var leadingTextRegExp = /\[(.+?)\]/g;
var leadingTextInfo = leadingTextRegExp.exec(string);
// did we find leading text, and if so, does it immediately precede the tag?
while (leadingTextInfo && leadingTextInfo.length) {
if (leadingTextInfo.index + leadingTextInfo[0].length === tagIndex) {
string = string.replace(leadingTextInfo[0], '');
leadingText = leadingTextInfo[1];
break;
}
leadingTextInfo = leadingTextRegExp.exec(string);
}
return {
leadingText: leadingText,
string: string
};
}
function processLink(string, tagInfo) {
var leading = extractLeadingText(string, tagInfo.completeTag);
var linkText = leading.leadingText;
var monospace;
var split;
var target;
string = leading.string;
split = splitLinkText(tagInfo.text);
target = split.target;
linkText = linkText || split.linkText;
monospace = useMonospace(tagInfo.tag, tagInfo.text);
return string.replace( tagInfo.completeTag, buildLink(target, linkText, {
linkMap: longnameToUrl,
monospace: monospace
}) );
}
function processTutorial(string, tagInfo) {
var leading = extractLeadingText(string, tagInfo.completeTag);
string = leading.string;
return string.replace( tagInfo.completeTag, toTutorial(tagInfo.text, leading.leadingText) );
}
var replacers = {
link: processLink,
linkcode: processLink,
linkplain: processLink,
tutorial: processTutorial
};
return replaceInlineTags(str, replacers).newString;
};
/** Convert tag text like "Jane Doe <jdoe@example.org>" into a mailto link */
exports.resolveAuthorLinks = function(str) {
var author;
var matches = str.match(/^\s?([\s\S]+)\b\s+<(\S+@\S+)>\s?$/);
if (matches && matches.length === 3) {
author = '<a href="mailto:' + matches[2] + '">' + htmlsafe(matches[1]) + '</a>';
}
else {
author = htmlsafe(str);
}
return author;
};
/**
* Find items in a TaffyDB database that match the specified key-value pairs.
* @param {TAFFY} data The TaffyDB database to search.
* @param {object|function} spec Key-value pairs to match against (for example,
* `{ longname: 'foo' }`), or a function that returns `true` if a value matches or `false` if it
* does not match.
* @return {array<object>} The matching items.
*/
var find = exports.find = function(data, spec) {
return data(spec).get();
};
/**
* Retrieve all of the following types of members from a set of doclets:
*
* + Classes
* + Externals
* + Globals
* + Mixins
* + Modules
* + Namespaces
* + Events
* @param {TAFFY} data The TaffyDB database to search.
* @return {object} An object with `classes`, `externals`, `globals`, `mixins`, `modules`,
* `events`, and `namespaces` properties. Each property contains an array of objects.
*/
exports.getMembers = function(data) {
var members = {
classes: find( data, {kind: 'class'} ),
externals: find( data, {kind: 'external'} ),
events: find( data, {kind: 'event'} ),
globals: find(data, {
kind: ['member', 'function', 'constant', 'typedef'],
memberof: { isUndefined: true }
}),
mixins: find( data, {kind: 'mixin'} ),
modules: find( data, {kind: 'module'} ),
namespaces: find( data, {kind: 'namespace'} ),
interfaces: find( data, {kind: 'interface'} )
};
// strip quotes from externals, since we allow quoted names that would normally indicate a
// namespace hierarchy (as in `@external "jquery.fn"`)
// TODO: we should probably be doing this for other types of symbols, here or elsewhere; see
// jsdoc3/jsdoc#396
members.externals = members.externals.map(function(doclet) {
doclet.name = doclet.name.replace(/(^"|"$)/g, '');
return doclet;
});
// functions that are also modules (as in `module.exports = function() {};`) are not globals
members.globals = members.globals.filter(function(doclet) {
return !isModuleExports(doclet);
});
return members;
};
/**
* Retrieve the member attributes for a doclet (for example, `virtual`, `static`, and
* `readonly`).
* @param {object} d The doclet whose attributes will be retrieved.
* @return {array<string>} The member attributes for the doclet.
*/
exports.getAttribs = function(d) {
var attribs = [];
if (!d) {
return attribs;
}
if (d.virtual) {
attribs.push('abstract');
}
if (d.access && d.access !== 'public') {
attribs.push(d.access);
}
if (d.scope && d.scope !== 'instance' && d.scope !== name.SCOPE.NAMES.GLOBAL) {
if (d.kind === 'function' || d.kind === 'member' || d.kind === 'constant') {
attribs.push(d.scope);
}
}
if (d.readonly === true) {
if (d.kind === 'member') {
attribs.push('readonly');
}
}
if (d.kind === 'constant') {
attribs.push('constant');
}
if (d.nullable === true) {
attribs.push('nullable');
}
else if (d.nullable === false) {
attribs.push('non-null');
}
return attribs;
};
/**
* Retrieve links to allowed types for the member.
*
* @param {Object} d - The doclet whose types will be retrieved.
* @param {string} [cssClass] - The CSS class to include in the `class` attribute for each link.
* @return {Array.<string>} HTML links to allowed types for the member.
*/
exports.getSignatureTypes = function(d, cssClass) {
var types = [];
if (d.type && d.type.names) {
types = d.type.names;
}
if (types && types.length) {
types = types.map(function(t) {
return linkto(t, htmlsafe(t), cssClass);
});
}
return types;
};
/**
* Retrieve names of the parameters that the member accepts. If a value is provided for `optClass`,
* the names of optional parameters will be wrapped in a `<span>` tag with that class.
* @param {object} d The doclet whose parameter names will be retrieved.
* @param {string} [optClass] The class to assign to the `<span>` tag that is wrapped around the
* names of optional parameters. If a value is not provided, optional parameter names will not be
* wrapped with a `<span>` tag. Must be a legal value for a CSS class name.
* @return {array<string>} An array of parameter names, with or without `<span>` tags wrapping the
* names of optional parameters.
*/
exports.getSignatureParams = function(d, optClass) {
var pnames = [];
if (d.params) {
d.params.forEach(function(p) {
if (p.name && p.name.indexOf('.') === -1) {
if (p.optional && optClass) {
pnames.push('<span class="' + optClass + '">' + p.name + '</span>');
}
else {
pnames.push(p.name);
}
}
});
}
return pnames;
};
/**
* Retrieve links to types that the member can return.
*
* @param {Object} d - The doclet whose types will be retrieved.
* @param {string} [cssClass] - The CSS class to include in the `class` attribute for each link.
* @return {Array.<string>} HTML links to types that the member can return.
*/
exports.getSignatureReturns = function(d, cssClass) {
var returnTypes = [];
if (d.returns) {
d.returns.forEach(function(r) {
if (r && r.type && r.type.names) {
if (!returnTypes.length) {
returnTypes = r.type.names;
}
}
});
}
if (returnTypes && returnTypes.length) {
returnTypes = returnTypes.map(function(r) {
return linkto(r, htmlsafe(r), cssClass);
});
}
return returnTypes;
};
/**
* Retrieve an ordered list of doclets for a symbol's ancestors.
*
* @param {TAFFY} data - The TaffyDB database to search.
* @param {Object} doclet - The doclet whose ancestors will be retrieved.
* @return {Array.<module:jsdoc/doclet.Doclet>} A array of ancestor doclets, sorted from most to
* least distant.
*/
exports.getAncestors = function(data, doclet) {
var ancestors = [];
var doc = doclet;
while (doc) {
doc = find(data, {longname: doc.memberof})[0];
if (doc) {
ancestors.unshift(doc);
}
}
return ancestors;
};
/**
* Retrieve links to a member's ancestors.
*
* @param {TAFFY} data - The TaffyDB database to search.
* @param {Object} doclet - The doclet whose ancestors will be retrieved.
* @param {string} [cssClass] - The CSS class to include in the `class` attribute for each link.
* @return {Array.<string>} HTML links to a member's ancestors.
*/
exports.getAncestorLinks = function(data, doclet, cssClass) {
var ancestors = exports.getAncestors(data, doclet);
var links = [];
ancestors.forEach(function(ancestor) {
var linkText = (exports.scopeToPunc[ancestor.scope] || '') + ancestor.name;
var link = linkto(ancestor.longname, linkText, cssClass);
links.push(link);
});
if (links.length) {
links[links.length - 1] += (exports.scopeToPunc[doclet.scope] || '');
}
return links;
};
/**
* Iterates through all the doclets in `data`, ensuring that if a method
* @listens to an event, then that event has a 'listeners' array with the
* longname of the listener in it.
*
* @param {TAFFY} data - The TaffyDB database to search.
*/
exports.addEventListeners = function(data) {
// TODO: do this on the *pruned* data
// find all doclets that @listen to something.
var listeners = find(data, function () { return this.listens && this.listens.length; });
if (!listeners.length) {
return;
}
var doc,
l,
_events = {}; // just a cache to prevent me doing so many lookups
listeners.forEach(function (listener) {
l = listener.listens;
l.forEach(function (eventLongname) {
doc = _events[eventLongname] || find(data, {longname: eventLongname, kind: 'event'})[0];
if (doc) {
if (!doc.listeners) {
doc.listeners = [listener.longname];
} else {
doc.listeners.push(listener.longname);
}
_events[eventLongname] = _events[eventLongname] || doc;
}
});
});
};
/**
* Remove members that will not be included in the output, including:
*
* + Undocumented members.
* + Members tagged `@ignore`.
* + Members of anonymous classes.
* + Members tagged `@private`, unless the `private` option is enabled.
* @param {TAFFY} data The TaffyDB database to prune.
* @return {TAFFY} The pruned database.
*/
exports.prune = function(data) {
data({undocumented: true}).remove();
data({ignore: true}).remove();
if (!env.opts.private) { data({access: 'private'}).remove(); }
data({memberof: '<anonymous>'}).remove();
return data;
};
/**
* Create a URL that points to the generated documentation for the doclet.
*
* If a doclet corresponds to an output file (for example, if the doclet represents a class), the
* URL will consist of a filename.
*
* If a doclet corresponds to a smaller portion of an output file (for example, if the doclet
* represents a method), the URL will consist of a filename and a fragment ID.
*
* @param {module:jsdoc/doclet.Doclet} doclet - The doclet that will be used to create the URL.
* @return {string} The URL to the generated documentation for the doclet.
*/
exports.createLink = function(doclet) {
var fakeContainer;
var filename;
var fileUrl;
var fragment = '';
var longname = doclet.longname;
var match;
// handle doclets in which doclet.longname implies that the doclet gets its own HTML file, but
// doclet.kind says otherwise. this happens due to mistagged JSDoc (for example, a module that
// somehow has doclet.kind set to `member`).
// TODO: generate a warning (ideally during parsing!)
if (containers.indexOf(doclet.kind) === -1) {
match = /(\S+):/.exec(longname);
if (match && containers.indexOf(match[1]) !== -1) {
fakeContainer = match[1];
}
}
// the doclet gets its own HTML file
if ( containers.indexOf(doclet.kind) !== -1 || isModuleExports(doclet) ) {
filename = getFilename(longname);
}
// mistagged version of a doclet that gets its own HTML file
else if ( containers.indexOf(doclet.kind) === -1 && fakeContainer ) {
filename = getFilename(doclet.memberof || longname);
if (doclet.name !== doclet.longname) {
fragment = formatNameForLink(doclet);
fragment = getId(longname, fragment);
}
}
// the doclet is within another HTML file
else {
filename = getFilename(doclet.memberof || exports.globalName);
if ( (doclet.name !== doclet.longname) || (doclet.scope === name.SCOPE.NAMES.GLOBAL) ) {
fragment = formatNameForLink(doclet);
fragment = getId(longname, fragment);
}
}
fileUrl = encodeURI( filename + fragmentHash(fragment) );
return fileUrl;
};
// TODO: docs
exports.longnamesToTree = name.longnamesToTree;
/**
* Replace the existing tag dictionary with a new tag dictionary.
*
* Used for testing only. Do not call this method directly. Instead, call
* {@link module:jsdoc/doclet._replaceDictionary}, which also updates this module's tag dictionary.
*
* @private
* @param {module:jsdoc/tag/dictionary.Dictionary} dict - The new tag dictionary.
*/
exports._replaceDictionary = function _replaceDictionary(dict) {
dictionary = dict;
};
+63
View File
@@ -0,0 +1,63 @@
'use strict';
var fs = require('fs');
var path = require('path');
var stream = require('stream');
var wrench = require('wrench');
var toDir = exports.toDir = function(_path) {
var isDirectory;
_path = path.normalize(_path);
try {
isDirectory = fs.statSync(_path).isDirectory();
}
catch(e) {
isDirectory = false;
}
if (isDirectory) {
return _path;
} else {
return path.dirname(_path);
}
};
exports.mkPath = function(/**Array*/ _path) {
if ( Array.isArray(_path) ) {
_path = _path.join('');
}
wrench.mkdirSyncRecursive(_path);
};
// adapted from http://procbits.com/2011/11/15/synchronous-file-copy-in-node-js
exports.copyFileSync = function(inFile, outDir, fileName) {
var BUF_LENGTH = 64 * 1024;
var read;
var write;
var buffer = new Buffer(BUF_LENGTH);
var bytesRead = 1;
var outFile = path.join( outDir, fileName || path.basename(inFile) );
var pos = 0;
wrench.mkdirSyncRecursive(outDir);
read = fs.openSync(inFile, 'r');
write = fs.openSync(outFile, 'w');
while (bytesRead > 0) {
bytesRead = fs.readSync(read, buffer, 0, BUF_LENGTH, pos);
fs.writeSync(write, buffer, 0, bytesRead);
pos += bytesRead;
}
fs.closeSync(read);
return fs.closeSync(write);
};
Object.keys(fs).forEach(function(key) {
exports[key] = fs[key];
});
+84
View File
@@ -0,0 +1,84 @@
{
"name": "jsdoc",
"version": "3.3.0-alpha13",
"revision": "1419376892674",
"description": "An API documentation generator for JavaScript.",
"keywords": [
"documentation",
"javascript"
],
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/jsdoc3/jsdoc"
},
"dependencies": {
"async": "~0.1.22",
"catharsis": "~0.8.5",
"escape-string-regexp": "~1.0.0",
"esprima": "https://github.com/ariya/esprima/tarball/49a2eccb243f29bd653b11e9419241a9d726af7c",
"js2xmlparser": "~0.1.0",
"marked": "~0.3.1",
"requizzle": "~0.2.0",
"strip-json-comments": "~0.1.3",
"taffydb": "https://github.com/hegemonic/taffydb/tarball/master",
"underscore": "~1.6.0",
"wrench": "~1.3.9"
},
"devDependencies": {
"eslint": "~0.10.2",
"gulp": "~3.8.5",
"gulp-eslint": "~0.1.7",
"gulp-json-editor": "~2.0.2",
"istanbul": "~0.2.1",
"tv4": "https://github.com/hegemonic/tv4/tarball/own-properties"
},
"engines": {
"node": ">=0.10"
},
"scripts": {
"test": "gulp test"
},
"bin": {
"jsdoc": "./jsdoc.js"
},
"bugs": {
"url": "https://github.com/jsdoc3/jsdoc/issues"
},
"author": {
"name": "Michael Mathews",
"email": "micmath@gmail.com"
},
"contributors": [
{
"url": "https://github.com/jsdoc3/jsdoc/graphs/contributors"
}
],
"maintainers": [
{
"name": "kzh",
"email": "kaleb@hornsby.ws"
},
{
"name": "hegemonic",
"email": "jeffrey.l.williams@gmail.com"
}
],
"gitHead": "3d17a1642a105d854ffad9758d43661b3b6e17f2",
"homepage": "https://github.com/jsdoc3/jsdoc",
"_id": "jsdoc@3.3.0-alpha13",
"_shasum": "ad64b989a4f6fbef3112ab1ee80908328a4ebfd2",
"_from": "jsdoc@<=3.3.0",
"_npmVersion": "1.4.28",
"_npmUser": {
"name": "hegemonic",
"email": "jeffrey.l.williams@gmail.com"
},
"dist": {
"shasum": "ad64b989a4f6fbef3112ab1ee80908328a4ebfd2",
"tarball": "http://registry.npmjs.org/jsdoc/-/jsdoc-3.3.0-alpha13.tgz"
},
"directories": {},
"_resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.3.0-alpha13.tgz",
"readme": "ERROR: No README data found!"
}
@@ -0,0 +1,23 @@
/**
@overview Demonstrate how to modify the source code before the parser sees it.
@module plugins/commentConvert
@author Michael Mathews <micmath@gmail.com>
*/
'use strict';
/*eslint spaced-line-comment: 0 */
exports.handlers = {
///
/// Convert ///-style comments into jsdoc comments.
/// @param e
/// @param e.filename
/// @param e.source
///
beforeParse: function(e) {
e.source = e.source.replace(/(\n[ \t]*\/\/\/[^\n]*)+/g, function($) {
var replacement = '\n/**' + $.replace(/^[ \t]*\/\/\//mg, '').replace(/(\n$|$)/, '*/$1');
return replacement;
});
}
};
@@ -0,0 +1,18 @@
/**
* @overview Remove everything in a file except JSDoc-style comments. By enabling this plugin, you
* can document source files that are not valid JavaScript (including source files for other
* languages).
* @module plugins/commentsOnly
* @author Jeff Williams <jeffrey.l.williams@gmail.com>
*/
'use strict';
exports.handlers = {
beforeParse: function(e) {
// a JSDoc comment looks like: /**[one or more chars]*/
var comments = e.source.match(/\/\*\*[\s\S]+?\*\//g);
if (comments) {
e.source = comments.join('\n\n');
}
}
};
@@ -0,0 +1,21 @@
/**
@overview Escape HTML tags in descriptions.
@module plugins/escapeHtml
@author Michael Mathews <micmath@gmail.com>
*/
'use strict';
exports.handlers = {
/**
Translate HTML tags in descriptions into safe entities.
Replaces <, & and newlines
*/
newDoclet: function(e) {
if (e.doclet.description) {
e.doclet.description = e.doclet.description
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/\r\n|\n|\r/g, '<br>');
}
}
};
@@ -0,0 +1,115 @@
/*global env: true */
/**
* @overview Dump information about parser events to the console.
* @module plugins/eventDumper
* @author Jeff Williams <jeffrey.l.williams@gmail.com>
*/
'use strict';
var _ = require('underscore');
var util = require('util');
var conf = env.conf.eventDumper || {};
var isRhino = require('jsdoc/util/runtime').isRhino();
// Dump the included parser events (defaults to all events)
var events = conf.include || [
'parseBegin',
'fileBegin',
'beforeParse',
'jsdocCommentFound',
'symbolFound',
'newDoclet',
'fileComplete',
'parseComplete',
'processingComplete'
];
// Don't dump the excluded parser events
if (conf.exclude) {
events = _.difference(events, conf.exclude);
}
/**
* Check whether a variable appears to be a Java native object.
*
* @param {*} o - The variable to check.
* @return {boolean} Set to `true` for Java native objects and `false` in all other cases.
*/
function isJavaNativeObject(o) {
if (!isRhino) {
return false;
}
return o && typeof o === 'object' && typeof o.getClass === 'function';
}
/**
* Replace AST node objects in events with a placeholder.
*
* @param {Object} o - An object whose properties may contain AST node objects.
* @return {Object} The modified object.
*/
function replaceNodeObjects(o) {
var doop = require('jsdoc/util/doop');
var OBJECT_PLACEHOLDER = '<Object>';
if (o.code && o.code.node) {
// don't break the original object!
o.code = doop(o.code);
o.code.node = OBJECT_PLACEHOLDER;
}
if (o.doclet && o.doclet.meta && o.doclet.meta.code && o.doclet.meta.code.node) {
// don't break the original object!
o.doclet.meta.code = doop(o.doclet.meta.code);
o.doclet.meta.code.node = OBJECT_PLACEHOLDER;
}
if (o.astnode) {
o.astnode = OBJECT_PLACEHOLDER;
}
return o;
}
/**
* Get rid of unwanted crud in an event object.
*
* @param {object} e The event object.
* @return {object} The fixed-up object.
*/
function cleanse(e) {
var result = {};
Object.keys(e).forEach(function(prop) {
// by default, don't stringify properties that contain an array of functions
if (!conf.includeFunctions && util.isArray(e[prop]) && e[prop][0] &&
String(typeof e[prop][0]) === 'function') {
result[prop] = 'function[' + e[prop].length + ']';
}
// never include functions that belong to the object
else if (typeof e[prop] !== 'function') {
// don't call JSON.stringify() on Java native objects--Rhino will throw an exception
result[prop] = isJavaNativeObject(e[prop]) ? String(e[prop]) : e[prop];
}
});
// allow users to omit node objects, which can be enormous
if (conf.omitNodes) {
result = replaceNodeObjects(result);
}
return result;
}
exports.handlers = {};
events.forEach(function(eventType) {
exports.handlers[eventType] = function(e) {
console.log( JSON.stringify({
type: eventType,
content: cleanse(e)
}, null, 4) );
};
});
@@ -0,0 +1,89 @@
/**
* @overview Translate doclet descriptions from MarkDown into HTML.
* @module plugins/markdown
* @author Michael Mathews <micmath@gmail.com>
* @author Ben Blank <ben.blank@gmail.com>
*/
'use strict';
var config = global.env.conf.markdown || {};
var defaultTags = [
'classdesc',
'description',
'exceptions',
'params',
'properties',
'returns',
'see'
];
var hasOwnProp = Object.prototype.hasOwnProperty;
var parse = require('jsdoc/util/markdown').getParser();
var tags = [];
var excludeTags = [];
function shouldProcessString(tagName, text) {
var shouldProcess = false;
if (tagName !== 'see') {
shouldProcess = true;
}
// we only want to process `@see` tags that contain Markdown links
else if (tagName === 'see' && text.indexOf('[') !== -1) {
shouldProcess = true;
}
return shouldProcess;
}
/**
* Process the markdown source in a doclet. The properties that should be
* processed are configurable, but always include "classdesc", "description",
* "params", "properties", and "returns". Handled properties can be bare
* strings, objects, or arrays of objects.
*/
function process(doclet) {
tags.forEach(function(tag) {
if ( !hasOwnProp.call(doclet, tag) ) {
return;
}
if (typeof doclet[tag] === 'string' && shouldProcessString(tag, doclet[tag]) ) {
doclet[tag] = parse(doclet[tag]);
}
else if ( Array.isArray(doclet[tag]) ) {
doclet[tag].forEach(function(value, index, original) {
var inner = {};
inner[tag] = value;
process(inner);
original[index] = inner[tag];
});
}
else if (doclet[tag]) {
process(doclet[tag]);
}
});
}
// set up the list of "tags" (properties) to process
if (config.tags) {
tags = config.tags.slice();
}
// set up the list of default tags to exclude from processing
if (config.excludeTags) {
excludeTags = config.excludeTags.slice();
}
defaultTags.forEach(function(tag) {
if (excludeTags.indexOf(tag) === -1 && tags.indexOf(tag) === -1) {
tags.push(tag);
}
});
exports.handlers = {
/**
* Translate markdown syntax in a new doclet's description into HTML. Is run
* by JSDoc 3 whenever a "newDoclet" event fires.
*/
newDoclet: function(e) {
process(e.doclet);
}
};
@@ -0,0 +1,185 @@
/**
* The Overload Helper plugin automatically adds a signature-like string to the longnames of
* overloaded functions and methods. In JSDoc, this string is known as a _variation_. (The longnames
* of overloaded constructor functions are _not_ updated, so that JSDoc can identify the class'
* members correctly.)
*
* Using this plugin allows you to link to overloaded functions without manually adding `@variation`
* tags to your documentation.
*
* For example, suppose your code includes a function named `foo` that you can call in the
* following ways:
*
* + `foo()`
* + `foo(bar)`
* + `foo(bar, baz)` (where `baz` is repeatable)
*
* This plugin assigns the following variations and longnames to each version of `foo`:
*
* + `foo()` gets the variation `()` and the longname `foo()`.
* + `foo(bar)` gets the variation `(bar)` and the longname `foo(bar)`.
* + `foo(bar, baz)` (where `baz` is repeatable) gets the variation `(bar, ...baz)` and the longname
* `foo(bar, ...baz)`.
*
* You can then link to these functions with `{@link foo()}`, `{@link foo(bar)}`, and
* `{@link foo(bar, ...baz)`. Note that the variation is based on the names of the function
* parameters, _not_ their types.
*
* If you prefer to manually assign variations to certain functions, you can still do so with the
* `@variation` tag. This plugin will not change these variations or add more variations for that
* function, as long as the variations you've defined result in unique longnames.
*
* If an overloaded function includes multiple signatures with the same parameter names, the plugin
* will assign numeric variations instead, starting at `(1)` and counting upwards.
*
* @module plugins/overloadHelper
* @author Jeff Williams <jeffrey.l.williams@gmail.com>
* @license Apache License 2.0
*/
'use strict';
// lookup table of function doclets by longname
var functionDoclets;
function hasUniqueValues(obj) {
var isUnique = true;
var seen = [];
Object.keys(obj).forEach(function(key) {
if (seen.indexOf(obj[key]) !== -1) {
isUnique = false;
}
seen.push(obj[key]);
});
return isUnique;
}
function getParamNames(params) {
var names = [];
params.forEach(function(param) {
var name = param.name || '';
if (param.variable) {
name = '...' + name;
}
if (name !== '') {
names.push(name);
}
});
return names.length ? names.join(', ') : '';
}
function getParamVariation(doclet) {
return getParamNames(doclet.params || []);
}
function getUniqueVariations(doclets) {
var counter = 0;
var variations = {};
var docletKeys = Object.keys(doclets);
function getUniqueNumbers() {
var format = require('util').format;
docletKeys.forEach(function(doclet) {
var newLongname;
while (true) {
counter++;
variations[doclet] = String(counter);
// is this longname + variation unique?
newLongname = format('%s(%s)', doclets[doclet].longname, variations[doclet]);
if ( !functionDoclets[newLongname] ) {
break;
}
}
});
}
function getUniqueNames() {
// start by trying to preserve existing variations
docletKeys.forEach(function(doclet) {
variations[doclet] = doclets[doclet].variation || getParamVariation(doclets[doclet]);
});
// if they're identical, try again, without preserving existing variations
if ( !hasUniqueValues(variations) ) {
docletKeys.forEach(function(doclet) {
variations[doclet] = getParamVariation(doclets[doclet]);
});
// if they're STILL identical, switch to numeric variations
if ( !hasUniqueValues(variations) ) {
getUniqueNumbers();
}
}
}
// are we already using numeric variations? if so, keep doing that
if (functionDoclets[doclets.newDoclet.longname + '(1)']) {
getUniqueNumbers();
}
else {
getUniqueNames();
}
return variations;
}
function ensureUniqueLongname(newDoclet) {
var doclets = {
oldDoclet: functionDoclets[newDoclet.longname],
newDoclet: newDoclet
};
var docletKeys = Object.keys(doclets);
var oldDocletLongname;
var variations = {};
if (doclets.oldDoclet) {
oldDocletLongname = doclets.oldDoclet.longname;
// if the shared longname has a variation, like MyClass#myLongname(variation),
// remove the variation
if (doclets.oldDoclet.variation || doclets.oldDoclet.variation === '') {
docletKeys.forEach(function(doclet) {
doclets[doclet].longname = doclets[doclet].longname.replace(/\([\s\S]*\)$/, '');
doclets[doclet].variation = null;
});
}
variations = getUniqueVariations(doclets);
// update the longnames/variations
docletKeys.forEach(function(doclet) {
doclets[doclet].longname += '(' + variations[doclet] + ')';
doclets[doclet].variation = variations[doclet];
});
// update the old doclet in the lookup table
functionDoclets[oldDocletLongname] = null;
functionDoclets[doclets.oldDoclet.longname] = doclets.oldDoclet;
}
// always store the new doclet in the lookup table
functionDoclets[doclets.newDoclet.longname] = doclets.newDoclet;
return doclets.newDoclet;
}
exports.handlers = {
parseBegin: function() {
functionDoclets = {};
},
newDoclet: function(e) {
if (e.doclet.kind === 'function') {
e.doclet = ensureUniqueLongname(e.doclet);
}
},
parseComplete: function() {
functionDoclets = null;
}
};
@@ -0,0 +1,31 @@
/**
@overview Adds support for reusable partial jsdoc files.
@module plugins/partial
@author Ludo Antonov <ludo@hulu.com>
*/
'use strict';
var fs = require('jsdoc/fs');
var path = require('path');
exports.handlers = {
/**
* Include a partial jsdoc
*
* @param e
* @param e.filename
* @param e.source
* @example
* @partial "partial_doc.jsdoc"
*/
beforeParse: function(e) {
e.source = e.source.replace(/(@partial \".*\")+/g, function($) {
var pathArg = $.match(/\".*\"/)[0].replace(/"/g, '');
var fullPath = path.join(e.filename, '..', pathArg);
var partialData = fs.readFileSync(fullPath, global.env.opts.encoding);
return partialData;
});
}
};
@@ -0,0 +1,20 @@
/**
@overview Strips the rails template tags from a js.erb file
@module plugins/railsTemplate
@author Jannon Frank <jannon@jannon.net>
*/
'use strict';
exports.handlers = {
/**
* Remove rails tags from the source input (e.g. <% foo bar %>)
* @param e
* @param e.filename
* @param e.source
*/
beforeParse: function(e) {
if (e.filename.match(/\.erb$/)) {
e.source = e.source.replace(/<%.*%>/g, '');
}
}
};
+17
View File
@@ -0,0 +1,17 @@
/**
@overview This is just an example.
@module plugins/shout
@author Michael Mathews <micmath@gmail.com>
*/
'use strict';
exports.handlers = {
/**
Make your descriptions more shoutier.
*/
newDoclet: function(e) {
if (typeof e.doclet.description === 'string') {
e.doclet.description = e.doclet.description.toUpperCase();
}
}
};
@@ -0,0 +1,51 @@
/**
@module plugins/sourcetag
@author Michael Mathews <micmath@gmail.com>
*/
'use strict';
var logger = require('jsdoc/util/logger');
exports.handlers = {
/**
Support @source tag. Expected value like:
{ "filename": "myfile.js", "lineno": 123 }
Modifies the corresponding meta values on the given doclet.
WARNING: If you are using a JSDoc template that generates pretty-printed source files,
such as JSDoc's default template, this plugin can cause JSDoc to crash. To fix this issue,
update your template settings to disable pretty-printed source files.
@source { "filename": "sourcetag.js", "lineno": 13 }
*/
newDoclet: function(e) {
var tags = e.doclet.tags,
tag,
value;
// any user-defined tags in this doclet?
if (typeof tags !== 'undefined') {
// only interested in the @source tags
tags = tags.filter(function($) {
return $.title === 'source';
});
if (tags.length) {
// take the first one
tag = tags[0];
try {
value = JSON.parse(tag.value);
}
catch(e) {
logger.error('@source tag expects a valid JSON value, like { "filename": "myfile.js", "lineno": 123 }.');
return;
}
e.doclet.meta = e.doclet.meta || {};
e.doclet.meta.filename = value.filename || '';
e.doclet.meta.lineno = value.lineno || '';
}
}
}
};
@@ -0,0 +1,61 @@
/**
* @overview This plugin creates a summary tag, if missing, from the first sentence in the
* description.
* @module plugins/summarize
* @author Mads Bondo Dydensborg <mbd@dbc.dk>
*/
'use strict';
exports.handlers = {
/**
* Autogenerate summaries, if missing, from the description, if present.
*/
newDoclet: function(e) {
var endTag;
var tags;
var stack;
// If the summary is missing, grab the first sentence from the description
// and use that.
if (e.doclet && !e.doclet.summary && e.doclet.description) {
// The summary may end with `.$`, `. `, or `.<` (a period followed by an HTML tag).
e.doclet.summary = e.doclet.description.split(/\.$|\.\s|\.</)[0];
// Append `.` as it was removed in both cases, or is possibly missing.
e.doclet.summary += '.';
// This is an excerpt of something that is possibly HTML.
// Balance it using a stack. Assume it was initially balanced.
tags = e.doclet.summary.match(/<[^>]+>/g) || [];
stack = [];
tags.forEach(function(tag) {
var idx = tag.indexOf('/');
if (idx === -1) {
// start tag -- push onto the stack
stack.push(tag);
} else if (idx === 1) {
// end tag -- pop off of the stack
stack.pop();
}
// otherwise, it's a self-closing tag; don't modify the stack
});
// stack should now contain only the start tags that lack end tags,
// with the most deeply nested start tag at the top
while (stack.length > 0) {
// pop the unmatched tag off the stack
endTag = stack.pop();
// get just the tag name
endTag = endTag.substring(1, endTag.search(/[ >]/));
// append the end tag
e.doclet.summary += '</' + endTag + '>';
}
// and, finally, if the summary starts and ends with a <p> tag, remove it; let the
// template decide whether to wrap the summary in a <p> tag
e.doclet.summary = e.doclet.summary.replace(/^<p>(.*)<\/p>$/i, '$1');
}
}
};
@@ -0,0 +1,24 @@
'use strict';
/**
* @see [Nowhere](http://nowhere.com)
*/
function foo() {}
/**
* @see AnObject#myProperty
*/
function bar() {}
/**
* @classdesc My class.
* @description My class.
* @exception {Error} Some error.
* @param {string} myParam - My parameter.
* @property {string} value - Value of myParam.
* @return {MyClass} Class instance.
* @see [Example Inc.](http://example.com)
*/
function MyClass(myParam) {
this.value = myParam;
}
@@ -0,0 +1,50 @@
/**
* A bowl of non-spicy soup.
* @class
*//**
* A bowl of spicy soup.
* @class
* @param {number} spiciness - The spiciness of the soup, in Scoville heat units (SHU).
*/
function Soup(spiciness) {}
/**
* Slurp the soup.
*//**
* Slurp the soup loudly.
* @param {number} dBA - The slurping volume, in A-weighted decibels.
*/
Soup.prototype.slurp = function(dBA) {};
/**
* Salt the soup as needed, using a highly optimized soup-salting heuristic.
*//**
* Salt the soup, specifying the amount of salt to add.
* @variation mg
* @param {number} amount - The amount of salt to add, in milligrams.
*/
Soup.prototype.salt = function(amount) {};
/**
* Heat the soup by the specified number of degrees.
* @param {number} degrees - The number of degrees, in Fahrenheit, by which to heat the soup.
*//**
* Heat the soup by the specified number of degrees.
* @variation 1
* @param {string} degrees - The number of degrees, in Fahrenheit, by which to heat the soup, but
* as a string for some reason.
*//**
* Heat the soup by the specified number of degrees.
* @param {boolean} degrees - The number of degrees, as a boolean. Wait, what?
*/
Soup.prototype.heat = function(degrees) {};
/**
* Discard the soup.
* @variation discardSoup
*//**
* Discard the soup by pouring it into the specified container.
* @variation discardSoup
* @param {Object} container - The container in which to discard the soup.
*/
Soup.prototype.discard = function(container) {};
@@ -0,0 +1,19 @@
/**
@overview Strips the rails template tags from a js.erb file
@module plugins/railsTemplate
@author Jannon Frank <jannon@jannon.net>
*/
exports.handlers = {
/**
* Remove rails tags from the source input (e.g. <% foo bar %>)
* @param e
* @param e.filename
* @param e.source
*/
beforeParse: function(e) {
if (e.filename.match(/\.erb$/)) {
e.source = e.source.replace(/<%.*%> /g, "");
}
}
};
@@ -0,0 +1,19 @@
/*global describe: true, env: true, expect: true, it: true, jasmine: true */
describe("commentConvert plugin", function() {
var parser = jasmine.createParser();
var path = require('jsdoc/path');
var docSet;
var pluginPath = 'plugins/commentConvert';
var pluginPathResolved = path.join(env.dirname, pluginPath);
var plugin = require(pluginPathResolved);
require('jsdoc/plugins').installPlugins([pluginPathResolved], parser);
docSet = jasmine.getDocSetFromFile(pluginPath + '.js', parser);
it("should convert '///-style comments into jsdoc comments", function() {
var doclet = docSet.getByLongname("module:plugins/commentConvert.handlers.beforeParse");
expect(doclet.length).toEqual(1);
});
});
@@ -0,0 +1,19 @@
/*global describe: true, env: true, expect: true, it: true, jasmine: true */
describe("escapeHtml plugin", function() {
var parser = jasmine.createParser();
var path = require('jsdoc/path');
var docSet;
var pluginPath = 'plugins/escapeHtml';
var pluginPathResolved = path.join(env.dirname,pluginPath);
var plugin = require(pluginPathResolved);
require('jsdoc/plugins').installPlugins([pluginPathResolved], parser);
docSet = jasmine.getDocSetFromFile(pluginPath + '.js', parser);
it("should escape '&', '<' and newlines in doclet descriptions", function() {
var doclet = docSet.getByLongname("module:plugins/escapeHtml.handlers.newDoclet");
expect(doclet[0].description).toEqual("Translate HTML tags in descriptions into safe entities.<br> Replaces &lt;, &amp; and newlines");
});
});
@@ -0,0 +1,48 @@
'use strict';
var path = require('jsdoc/path');
describe('markdown plugin', function() {
var pluginPath = 'plugins/markdown';
var pluginPathResolved = path.join(global.env.dirname, pluginPath);
var plugin = require(pluginPathResolved);
var docSet = jasmine.getDocSetFromFile('plugins/test/fixtures/markdown.js');
// TODO: more tests; refactor the plugin so multiple settings can be tested
it('should process the correct tags by default', function() {
var myClass = docSet.getByLongname('MyClass')[0];
plugin.handlers.newDoclet({ doclet: myClass });
[
myClass.classdesc,
myClass.description,
myClass.exceptions[0].description,
myClass.params[0].description,
myClass.properties[0].description,
myClass.returns[0].description,
myClass.see
].forEach(function(value) {
// if we processed the value, it should be wrapped in a <p> tag
expect( /^<p>(?:.+)<\/p>$/.test(value) ).toBe(true);
});
});
describe('@see tag support', function() {
var foo = docSet.getByLongname('foo')[0];
var bar = docSet.getByLongname('bar')[0];
it('should parse @see tags containing links', function() {
plugin.handlers.newDoclet({ doclet: foo });
expect(typeof foo).toEqual('object');
expect(foo.see[0]).toEqual('<p><a href="http://nowhere.com">Nowhere</a></p>');
});
it('should not parse @see tags that do not contain links', function() {
plugin.handlers.newDoclet({ doclet: bar });
expect(typeof bar).toEqual('object');
expect(bar.see[0]).toEqual('AnObject#myProperty');
});
});
});
@@ -0,0 +1,101 @@
/*global describe: true, env: true, expect: true, it: true, jasmine: true, xit: true */
describe('plugins/overloadHelper', function() {
var parser = jasmine.createParser();
var path = require('jsdoc/path');
var docSet;
var pluginPath = 'plugins/overloadHelper';
var pluginPathResolved = path.resolve(env.dirname, pluginPath);
var plugin = require(pluginPathResolved);
require('jsdoc/plugins').installPlugins([pluginPathResolved], parser);
docSet = jasmine.getDocSetFromFile('plugins/test/fixtures/overloadHelper.js', parser);
it('should exist', function() {
expect(plugin).toBeDefined();
expect(typeof plugin).toBe('object');
});
it('should export handlers', function() {
expect(plugin.handlers).toBeDefined();
expect(typeof plugin.handlers).toBe('object');
});
it('should export a "newDoclet" handler', function() {
expect(plugin.handlers.newDoclet).toBeDefined();
expect(typeof plugin.handlers.newDoclet).toBe('function');
});
it('should export a "parseComplete" handler', function() {
expect(plugin.handlers.parseComplete).toBeDefined();
expect(typeof plugin.handlers.parseComplete).toBe('function');
});
describe('newDoclet handler', function() {
it('should not add unique longnames to constructors', function() {
var soup = docSet.getByLongname('Soup');
var soup1 = docSet.getByLongname('Soup()');
var soup2 = docSet.getByLongname('Soup(spiciness)');
expect(soup.length).toBe(2);
expect(soup1.length).toBe(0);
expect(soup2.length).toBe(0);
});
it('should add unique longnames to methods', function() {
var slurp = docSet.getByLongname('Soup#slurp');
var slurp1 = docSet.getByLongname('Soup#slurp()');
var slurp2 = docSet.getByLongname('Soup#slurp(dBA)');
expect(slurp.length).toBe(0);
expect(slurp1.length).toBe(1);
expect(slurp2.length).toBe(1);
});
it('should update the "variation" property of the method', function() {
var slurp1 = docSet.getByLongname('Soup#slurp()')[0];
var slurp2 = docSet.getByLongname('Soup#slurp(dBA)')[0];
expect(slurp1.variation).toBe('');
expect(slurp2.variation).toBe('dBA');
});
it('should not add to or change existing variations that are unique', function() {
var salt1 = docSet.getByLongname('Soup#salt');
var salt2 = docSet.getByLongname('Soup#salt(mg)');
expect(salt1.length).toBe(1);
expect(salt2.length).toBe(1);
});
it('should not duplicate the names of existing numeric variations', function() {
var heat1 = docSet.getByLongname('Soup#heat(1)');
var heat2 = docSet.getByLongname('Soup#heat(2)');
var heat3 = docSet.getByLongname('Soup#heat(3)');
expect(heat1.length).toBe(1);
expect(heat2.length).toBe(1);
expect(heat3.length).toBe(1);
});
it('should replace identical variations with new, unique variations', function() {
var discard1 = docSet.getByLongname('Soup#discard()');
var discard2 = docSet.getByLongname('Soup#discard(container)');
expect(discard1.length).toBe(1);
expect(discard2.length).toBe(1);
});
});
describe('parseComplete handler', function() {
// disabled because on the second run, each comment is being parsed twice; who knows why...
xit('should not retain parse results between parser runs', function() {
parser.clear();
docSet = jasmine.getDocSetFromFile('plugins/test/fixtures/overloadHelper.js', parser);
var heat = docSet.getByLongname('Soup#heat(4)');
expect(heat.length).toBe(0);
});
});
});
@@ -0,0 +1,17 @@
/*global describe: true, env: true, expect: true, it: true, jasmine: true */
describe("railsTemplate plugin", function() {
var parser = jasmine.createParser();
var path = require('jsdoc/path');
var pluginPath = path.join(env.dirname, 'plugins/railsTemplate');
var plugin = require(pluginPath);
require('jsdoc/plugins').installPlugins([pluginPath], parser);
require('jsdoc/src/handlers').attachTo(parser);
it("should remove <% %> rails template tags from the source of *.erb files", function() {
var docSet = parser.parse([path.join(env.dirname, "plugins/test/fixtures/railsTemplate.js.erb")]);
expect(docSet[2].description).toEqual("Remove rails tags from the source input (e.g. )");
});
});
@@ -0,0 +1,19 @@
/*global describe: true, env: true, expect: true, it: true, jasmine: true */
describe("shout plugin", function() {
var parser = jasmine.createParser();
var path = require('jsdoc/path');
var docSet;
var pluginPath = 'plugins/shout';
var pluginPathResolved = path.join(env.dirname, pluginPath);
var plugin = require(pluginPathResolved);
require('jsdoc/plugins').installPlugins([pluginPathResolved], parser);
docSet = jasmine.getDocSetFromFile(pluginPath + '.js', parser);
it("should make the description uppercase", function() {
var doclet = docSet.getByLongname("module:plugins/shout.handlers.newDoclet");
expect(doclet[0].description).toEqual("MAKE YOUR DESCRIPTIONS MORE SHOUTIER.");
});
});
@@ -0,0 +1,23 @@
/*global describe, expect, it, jasmine */
'use strict';
describe('sourcetag plugin', function() {
var parser = jasmine.createParser();
var path = require('jsdoc/path');
var docSet;
var pluginPath = 'plugins/sourcetag';
var pluginPathResolved = path.join(global.env.dirname, pluginPath);
var plugin = require(pluginPathResolved);
require('jsdoc/plugins').installPlugins([pluginPathResolved], parser);
docSet = jasmine.getDocSetFromFile(pluginPath + '.js', parser);
it("should set the lineno and filename of the doclet's meta property", function() {
var doclet = docSet.getByLongname('module:plugins/sourcetag.handlers.newDoclet');
expect(doclet[0].meta).toBeDefined();
expect(doclet[0].meta.filename).toEqual('sourcetag.js');
expect(doclet[0].meta.lineno).toEqual(13);
});
});
@@ -0,0 +1,112 @@
/*global describe, expect, it */
'use strict';
var summarize = require('../../summarize');
describe('summarize', function() {
it('should export handlers', function() {
expect(summarize.handlers).toBeDefined();
expect(typeof summarize.handlers).toBe('object');
});
it('should export a newDoclet handler', function() {
expect(summarize.handlers.newDoclet).toBeDefined();
expect(typeof summarize.handlers.newDoclet).toBe('function');
});
describe('newDoclet handler', function() {
var handler = summarize.handlers.newDoclet;
it('should not blow up if the doclet is missing', function() {
function noDoclet() {
return handler({});
}
expect(noDoclet).not.toThrow();
});
it('should not change the summary if it is already defined', function() {
var doclet = {
summary: 'This is a summary.',
description: 'Descriptions are good.'
};
handler({ doclet: doclet });
expect(doclet.summary).not.toBe(doclet.description);
});
it('should not do anything if the description is missing', function() {
var doclet = {};
handler({ doclet: doclet });
expect(doclet.summary).not.toBeDefined();
});
it('should use the first sentence as the summary', function() {
var doclet = {
description: 'This sentence is the summary. This sentence is not.'
};
handler({ doclet: doclet });
expect(doclet.summary).toBe('This sentence is the summary.');
});
it('should not add an extra period if there is only one sentence in the description',
function() {
var doclet = {
description: 'This description has only one sentence.'
};
handler({ doclet: doclet });
expect(doclet.summary).toBe('This description has only one sentence.');
});
it('should use the entire description, plus a period, as the summary if the description ' +
'does not contain a period', function() {
var doclet = {
description: 'This is a description'
};
handler({ doclet: doclet });
expect(doclet.summary).toBe('This is a description.');
});
it('should use the entire description as the summary if the description contains only ' +
'one sentence', function() {
var doclet = {
description: 'This is a description.'
};
handler({ doclet: doclet });
expect(doclet.description).toBe('This is a description.');
});
it('should work when an HTML tag immediately follows the first sentence', function() {
var doclet = {
description: 'This sentence is the summary.<small>This sentence is small.</small>'
};
handler({ doclet: doclet });
expect(doclet.summary).toBe('This sentence is the summary.');
});
it('should generate valid HTML if a tag is opened, but not closed, in the summary',
function() {
var doclet = {
description: 'This description has <em>a tag. The tag straddles</em> sentences.'
};
handler({ doclet: doclet });
expect(doclet.summary).toBe('This description has <em>a tag.</em>');
});
it('should not include a <p> tag in the summary', function() {
var doclet = {
description: '<p>This description contains HTML.</p><p>And plenty of it!</p>'
};
handler({ doclet: doclet });
expect(doclet.summary).toBe('This description contains HTML.');
});
});
});
@@ -0,0 +1,27 @@
To create or use your own template:
1. Create a folder with the same name as your template (for example, `mycooltemplate`).
2. Within the template folder, create a file named `publish.js`. This file must be a CommonJS module that exports a method named `publish`.
For example:
````javascript
/** @module publish */
/**
* Generate documentation output.
*
* @param {TAFFY} data - A TaffyDB collection representing
* all the symbols documented in your code.
* @param {object} opts - An object with options information.
*/
exports.publish = function(data, opts) {
// do stuff here to generate your output files
};
````
To invoke JSDoc 3 with your own template, use the `-t` command line option, and specify the path to your template folder:
````
./jsdoc mycode.js -t /path/to/mycooltemplate
````
@@ -0,0 +1 @@
The default template for JSDoc 3 uses: [the Taffy Database library](http://taffydb.com/) and the [Underscore Template library](http://documentcloud.github.com/underscore/#template).
@@ -0,0 +1,633 @@
/*global env: true */
'use strict';
var doop = require('jsdoc/util/doop');
var fs = require('jsdoc/fs');
var helper = require('jsdoc/util/templateHelper');
var logger = require('jsdoc/util/logger');
var path = require('jsdoc/path');
var taffy = require('taffydb').taffy;
var template = require('jsdoc/template');
var util = require('util');
var htmlsafe = helper.htmlsafe;
var linkto = helper.linkto;
var resolveAuthorLinks = helper.resolveAuthorLinks;
var scopeToPunc = helper.scopeToPunc;
var hasOwnProp = Object.prototype.hasOwnProperty;
var data;
var view;
var outdir = path.normalize(env.opts.destination);
function find(spec) {
return helper.find(data, spec);
}
function tutoriallink(tutorial) {
return helper.toTutorial(tutorial, null, { tag: 'em', classname: 'disabled', prefix: 'Tutorial: ' });
}
function getAncestorLinks(doclet) {
return helper.getAncestorLinks(data, doclet);
}
function hashToLink(doclet, hash) {
if ( !/^(#.+)/.test(hash) ) { return hash; }
var url = helper.createLink(doclet);
url = url.replace(/(#.+|$)/, hash);
return '<a href="' + url + '">' + hash + '</a>';
}
function needsSignature(doclet) {
var needsSig = false;
// function and class definitions always get a signature
if (doclet.kind === 'function' || doclet.kind === 'class') {
needsSig = true;
}
// typedefs that contain functions get a signature, too
else if (doclet.kind === 'typedef' && doclet.type && doclet.type.names &&
doclet.type.names.length) {
for (var i = 0, l = doclet.type.names.length; i < l; i++) {
if (doclet.type.names[i].toLowerCase() === 'function') {
needsSig = true;
break;
}
}
}
return needsSig;
}
function getSignatureAttributes(item) {
var attributes = [];
if (item.optional) {
attributes.push('opt');
}
if (item.nullable === true) {
attributes.push('nullable');
}
else if (item.nullable === false) {
attributes.push('non-null');
}
return attributes;
}
function updateItemName(item) {
var attributes = getSignatureAttributes(item);
var itemName = item.name || '';
if (item.variable) {
itemName = '&hellip;' + itemName;
}
if (attributes && attributes.length) {
itemName = util.format( '%s<span class="signature-attributes">%s</span>', itemName,
attributes.join(', ') );
}
return itemName;
}
function addParamAttributes(params) {
return params.filter(function(param) {
return param.name && param.name.indexOf('.') === -1;
}).map(updateItemName);
}
function buildItemTypeStrings(item) {
var types = [];
if (item && item.type && item.type.names) {
item.type.names.forEach(function(name) {
types.push( linkto(name, htmlsafe(name)) );
});
}
return types;
}
function buildAttribsString(attribs) {
var attribsString = '';
if (attribs && attribs.length) {
attribsString = htmlsafe( util.format('(%s) ', attribs.join(', ')) );
}
return attribsString;
}
function addNonParamAttributes(items) {
var types = [];
items.forEach(function(item) {
types = types.concat( buildItemTypeStrings(item) );
});
return types;
}
function addSignatureParams(f) {
var params = f.params ? addParamAttributes(f.params) : [];
f.signature = util.format( '%s(%s)', (f.signature || ''), params.join(', ') );
}
function addSignatureReturns(f) {
var attribs = [];
var attribsString = '';
var returnTypes = [];
var returnTypesString = '';
// jam all the return-type attributes into an array. this could create odd results (for example,
// if there are both nullable and non-nullable return types), but let's assume that most people
// who use multiple @return tags aren't using Closure Compiler type annotations, and vice-versa.
if (f.returns) {
f.returns.forEach(function(item) {
helper.getAttribs(item).forEach(function(attrib) {
if (attribs.indexOf(attrib) === -1) {
attribs.push(attrib);
}
});
});
attribsString = buildAttribsString(attribs);
}
if (f.returns) {
returnTypes = addNonParamAttributes(f.returns);
}
if (returnTypes.length) {
returnTypesString = util.format( ' &rarr; %s{%s}', attribsString, returnTypes.join('|') );
}
f.signature = '<span class="signature">' + (f.signature || '') + '</span>' +
'<span class="type-signature">' + returnTypesString + '</span>';
}
function addSignatureTypes(f) {
var types = f.type ? buildItemTypeStrings(f) : [];
f.signature = (f.signature || '') + '<span class="type-signature">' +
(types.length ? ' :' + types.join('|') : '') + '</span>';
}
function addAttribs(f) {
var attribs = helper.getAttribs(f);
var attribsString = buildAttribsString(attribs);
f.attribs = util.format('<span class="type-signature">%s</span>', attribsString);
}
function shortenPaths(files, commonPrefix) {
Object.keys(files).forEach(function(file) {
files[file].shortened = files[file].resolved.replace(commonPrefix, '')
// always use forward slashes
.replace(/\\/g, '/');
});
return files;
}
function getPathFromDoclet(doclet) {
if (!doclet.meta) {
return null;
}
return doclet.meta.path && doclet.meta.path !== 'null' ?
path.join(doclet.meta.path, doclet.meta.filename) :
doclet.meta.filename;
}
function generate(title, docs, filename, resolveLinks) {
resolveLinks = resolveLinks === false ? false : true;
var docData = {
title: title,
docs: docs
};
var outpath = path.join(outdir, filename),
html = view.render('container.tmpl', docData);
if (resolveLinks) {
html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
}
fs.writeFileSync(outpath, html, 'utf8');
}
function generateSourceFiles(sourceFiles, encoding) {
encoding = encoding || 'utf8';
Object.keys(sourceFiles).forEach(function(file) {
var source;
// links are keyed to the shortened path in each doclet's `meta.shortpath` property
var sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened);
helper.registerLink(sourceFiles[file].shortened, sourceOutfile);
try {
source = {
kind: 'source',
code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, encoding) )
};
}
catch(e) {
logger.error('Error while generating source file %s: %s', file, e.message);
}
generate('Source: ' + sourceFiles[file].shortened, [source], sourceOutfile,
false);
});
}
/**
* Look for classes or functions with the same name as modules (which indicates that the module
* exports only that class or function), then attach the classes or functions to the `module`
* property of the appropriate module doclets. The name of each class or function is also updated
* for display purposes. This function mutates the original arrays.
*
* @private
* @param {Array.<module:jsdoc/doclet.Doclet>} doclets - The array of classes and functions to
* check.
* @param {Array.<module:jsdoc/doclet.Doclet>} modules - The array of module doclets to search.
*/
function attachModuleSymbols(doclets, modules) {
var symbols = {};
// build a lookup table
doclets.forEach(function(symbol) {
symbols[symbol.longname] = symbols[symbol.longname] || [];
symbols[symbol.longname].push(symbol);
});
return modules.map(function(module) {
if (symbols[module.longname]) {
module.modules = symbols[module.longname].map(function(symbol) {
symbol = doop(symbol);
if (symbol.kind === 'class' || symbol.kind === 'function') {
symbol.name = symbol.name.replace('module:', '(require("') + '"))';
}
return symbol;
});
}
});
}
function buildMemberNav(items, itemHeading, itemsSeen) {
var nav = '';
if (items.length) {
var itemsNav = '';
items.forEach(function(item) {
if ( !hasOwnProp.call(itemsSeen, item.longname) ) {
itemsNav += '<li>' + linkto(item.longname, item.name.replace(/^module:/, '')) + '</li>';
itemsSeen[item.longname] = true;
}
});
if (itemsNav !== '') {
nav += '<h3>' + itemHeading + '</h3><ul>' + itemsNav + '</ul>';
}
}
return nav;
}
function linktoTutorial(longName, name) {
return tutoriallink(name);
}
function linktoExternal(longName, name) {
return linkto(longName, name.replace(/(^"|"$)/g, ''));
}
/**
* Create the navigation sidebar.
* @param {object} members The members that will be used to create the sidebar.
* @param {array<object>} members.classes
* @param {array<object>} members.externals
* @param {array<object>} members.globals
* @param {array<object>} members.mixins
* @param {array<object>} members.modules
* @param {array<object>} members.namespaces
* @param {array<object>} members.tutorials
* @param {array<object>} members.events
* @param {array<object>} members.interfaces
* @return {string} The HTML for the navigation sidebar.
*/
function buildNav(members) {
var nav = '<h2><a href="index.html">Home</a></h2>',
seen = {};
nav += buildMemberNav(members.modules, 'Modules', {}, linkto);
nav += buildMemberNav(members.externals, 'Externals', seen, linktoExternal);
nav += buildMemberNav(members.classes, 'Classes', seen, linkto);
nav += buildMemberNav(members.events, 'Events', seen, linkto);
nav += buildMemberNav(members.namespaces, 'Namespaces', seen, linkto);
nav += buildMemberNav(members.mixins, 'Mixins', seen, linkto);
nav += buildMemberNav(members.tutorials, 'Tutorials', seen, linktoTutorial);
nav += buildMemberNav(members.interfaces, 'Interfaces', seen, linkto);
if (members.globals.length) {
var globalNav = '';
members.globals.forEach(function(g) {
if ( g.kind !== 'typedef' && !hasOwnProp.call(seen, g.longname) ) {
globalNav += '<li>' + linkto(g.longname, g.name) + '</li>';
}
seen[g.longname] = true;
});
if (!globalNav) {
// turn the heading into a link so you can actually get to the global page
nav += '<h3>' + linkto('global', 'Global') + '</h3>';
}
else {
nav += '<h3>Global</h3><ul>' + globalNav + '</ul>';
}
}
return nav;
}
/**
@param {TAFFY} taffyData See <http://taffydb.com/>.
@param {object} opts
@param {Tutorial} tutorials
*/
exports.publish = function(taffyData, opts, tutorials) {
data = taffyData;
var conf = env.conf.templates || {};
conf['default'] = conf['default'] || {};
var templatePath = path.normalize(opts.template);
view = new template.Template( path.join(templatePath, 'tmpl') );
// claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness
// doesn't try to hand them out later
var indexUrl = helper.getUniqueFilename('index');
// don't call registerLink() on this one! 'index' is also a valid longname
var globalUrl = helper.getUniqueFilename('global');
helper.registerLink('global', globalUrl);
// set up templating
view.layout = conf['default'].layoutFile ?
path.getResourcePath(path.dirname(conf['default'].layoutFile),
path.basename(conf['default'].layoutFile) ) :
'layout.tmpl';
// set up tutorials for helper
helper.setTutorials(tutorials);
data = helper.prune(data);
data.sort('longname, version, since');
helper.addEventListeners(data);
var sourceFiles = {};
var sourceFilePaths = [];
data().each(function(doclet) {
doclet.attribs = '';
if (doclet.examples) {
doclet.examples = doclet.examples.map(function(example) {
var caption, code;
if (example.match(/^\s*<caption>([\s\S]+?)<\/caption>(\s*[\n\r])([\s\S]+)$/i)) {
caption = RegExp.$1;
code = RegExp.$3;
}
return {
caption: caption || '',
code: code || example
};
});
}
if (doclet.see) {
doclet.see.forEach(function(seeItem, i) {
doclet.see[i] = hashToLink(doclet, seeItem);
});
}
// build a list of source files
var sourcePath;
if (doclet.meta) {
sourcePath = getPathFromDoclet(doclet);
sourceFiles[sourcePath] = {
resolved: sourcePath,
shortened: null
};
if (sourceFilePaths.indexOf(sourcePath) === -1) {
sourceFilePaths.push(sourcePath);
}
}
});
// update outdir if necessary, then create outdir
var packageInfo = ( find({kind: 'package'}) || [] ) [0];
if (packageInfo && packageInfo.name) {
outdir = path.join( outdir, packageInfo.name, (packageInfo.version || '') );
}
fs.mkPath(outdir);
// copy the template's static files to outdir
var fromDir = path.join(templatePath, 'static');
var staticFiles = fs.ls(fromDir, 3);
staticFiles.forEach(function(fileName) {
var toDir = fs.toDir( fileName.replace(fromDir, outdir) );
fs.mkPath(toDir);
fs.copyFileSync(fileName, toDir);
});
// copy user-specified static files to outdir
var staticFilePaths;
var staticFileFilter;
var staticFileScanner;
if (conf['default'].staticFiles) {
// The canonical property name is `include`. We accept `paths` for backwards compatibility
// with a bug in JSDoc 3.2.x.
staticFilePaths = conf['default'].staticFiles.include ||
conf['default'].staticFiles.paths ||
[];
staticFileFilter = new (require('jsdoc/src/filter')).Filter(conf['default'].staticFiles);
staticFileScanner = new (require('jsdoc/src/scanner')).Scanner();
staticFilePaths.forEach(function(filePath) {
var extraStaticFiles = staticFileScanner.scan([filePath], 10, staticFileFilter);
extraStaticFiles.forEach(function(fileName) {
var sourcePath = fs.toDir(filePath);
var toDir = fs.toDir( fileName.replace(sourcePath, outdir) );
fs.mkPath(toDir);
fs.copyFileSync(fileName, toDir);
});
});
}
if (sourceFilePaths.length) {
sourceFiles = shortenPaths( sourceFiles, path.commonPrefix(sourceFilePaths) );
}
data().each(function(doclet) {
var url = helper.createLink(doclet);
helper.registerLink(doclet.longname, url);
// add a shortened version of the full path
var docletPath;
if (doclet.meta) {
docletPath = getPathFromDoclet(doclet);
docletPath = sourceFiles[docletPath].shortened;
if (docletPath) {
doclet.meta.shortpath = docletPath;
}
}
});
data().each(function(doclet) {
var url = helper.longnameToUrl[doclet.longname];
if (url.indexOf('#') > -1) {
doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop();
}
else {
doclet.id = doclet.name;
}
if ( needsSignature(doclet) ) {
addSignatureParams(doclet);
addSignatureReturns(doclet);
addAttribs(doclet);
}
});
// do this after the urls have all been generated
data().each(function(doclet) {
doclet.ancestors = getAncestorLinks(doclet);
if (doclet.kind === 'member') {
addSignatureTypes(doclet);
addAttribs(doclet);
}
if (doclet.kind === 'constant') {
addSignatureTypes(doclet);
addAttribs(doclet);
doclet.kind = 'member';
}
});
var members = helper.getMembers(data);
members.tutorials = tutorials.children;
// output pretty-printed source files by default
var outputSourceFiles = conf['default'] && conf['default'].outputSourceFiles !== false ? true :
false;
// add template helpers
view.find = find;
view.linkto = linkto;
view.resolveAuthorLinks = resolveAuthorLinks;
view.tutoriallink = tutoriallink;
view.htmlsafe = htmlsafe;
view.outputSourceFiles = outputSourceFiles;
// once for all
view.nav = buildNav(members);
attachModuleSymbols( find({ longname: {left: 'module:'} }), members.modules );
// generate the pretty-printed source files first so other pages can link to them
if (outputSourceFiles) {
generateSourceFiles(sourceFiles, opts.encoding);
}
if (members.globals.length) { generate('Global', [{kind: 'globalobj'}], globalUrl); }
// index page displays information from package.json and lists files
var files = find({kind: 'file'}),
packages = find({kind: 'package'});
generate('Home',
packages.concat(
[{kind: 'mainpage', readme: opts.readme, longname: (opts.mainpagetitle) ? opts.mainpagetitle : 'Main Page'}]
).concat(files),
indexUrl);
// set up the lists that we'll use to generate pages
var classes = taffy(members.classes);
var modules = taffy(members.modules);
var namespaces = taffy(members.namespaces);
var mixins = taffy(members.mixins);
var externals = taffy(members.externals);
var interfaces = taffy(members.interfaces);
Object.keys(helper.longnameToUrl).forEach(function(longname) {
var myModules = helper.find(modules, {longname: longname});
if (myModules.length) {
generate('Module: ' + myModules[0].name, myModules, helper.longnameToUrl[longname]);
}
var myClasses = helper.find(classes, {longname: longname});
if (myClasses.length) {
generate('Class: ' + myClasses[0].name, myClasses, helper.longnameToUrl[longname]);
}
var myNamespaces = helper.find(namespaces, {longname: longname});
if (myNamespaces.length) {
generate('Namespace: ' + myNamespaces[0].name, myNamespaces, helper.longnameToUrl[longname]);
}
var myMixins = helper.find(mixins, {longname: longname});
if (myMixins.length) {
generate('Mixin: ' + myMixins[0].name, myMixins, helper.longnameToUrl[longname]);
}
var myExternals = helper.find(externals, {longname: longname});
if (myExternals.length) {
generate('External: ' + myExternals[0].name, myExternals, helper.longnameToUrl[longname]);
}
var myInterfaces = helper.find(interfaces, {longname: longname});
if (myInterfaces.length) {
generate('Interface: ' + myInterfaces[0].name, myInterfaces, helper.longnameToUrl[longname]);
}
});
// TODO: move the tutorial functions to templateHelper.js
function generateTutorial(title, tutorial, filename) {
var tutorialData = {
title: title,
header: tutorial.title,
content: tutorial.parse(),
children: tutorial.children
};
var tutorialPath = path.join(outdir, filename),
html = view.render('tutorial.tmpl', tutorialData);
// yes, you can use {@link} in tutorials too!
html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
fs.writeFileSync(tutorialPath, html, 'utf8');
}
// tutorials can have only one parent so there is no risk for loops
function saveChildren(node) {
node.children.forEach(function(child) {
generateTutorial('Tutorial: ' + child.title, child, helper.tutorialToUrl(child.name));
saveChildren(child);
});
}
saveChildren(tutorials);
};
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 116 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 118 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 114 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 117 KiB

@@ -0,0 +1,25 @@
/*global document */
(function() {
var source = document.getElementsByClassName('prettyprint source linenums');
var i = 0;
var lineNumber = 0;
var lineId;
var lines;
var totalLines;
var anchorHash;
if (source && source[0]) {
anchorHash = document.location.hash.substring(1);
lines = source[0].getElementsByTagName('li');
totalLines = lines.length;
for (; i < totalLines; i++) {
lineNumber++;
lineId = 'line' + lineNumber;
lines[i].id = lineId;
if (lineId === anchorHash) {
lines[i].className += ' selected';
}
}
}
})();
@@ -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.
@@ -0,0 +1,2 @@
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
@@ -0,0 +1,28 @@
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
@@ -0,0 +1,351 @@
@font-face {
font-family: 'Open Sans';
font-weight: normal;
font-style: normal;
src: url('../fonts/OpenSans-Regular-webfont.eot');
src:
local('Open Sans'),
local('OpenSans'),
url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Regular-webfont.woff') format('woff'),
url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg');
}
@font-face {
font-family: 'Open Sans Light';
font-weight: normal;
font-style: normal;
src: url('../fonts/OpenSans-Light-webfont.eot');
src:
local('Open Sans Light'),
local('OpenSans Light'),
url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Light-webfont.woff') format('woff'),
url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg');
}
html
{
overflow: auto;
background-color: #fff;
font-size: 14px;
}
body
{
font-family: 'Open Sans', sans-serif;
line-height: 1.5;
color: #4d4e53;
background-color: white;
}
a, a:visited, a:active {
color: #0095dd;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
header
{
display: block;
padding: 0px 4px;
}
tt, code, kbd, samp {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
}
.class-description {
font-size: 130%;
line-height: 140%;
margin-bottom: 1em;
margin-top: 1em;
}
.class-description:empty {
margin: 0;
}
#main {
float: left;
width: 70%;
}
article dl {
margin-bottom: 40px;
}
section
{
display: block;
background-color: #fff;
padding: 12px 24px;
border-bottom: 1px solid #ccc;
margin-right: 30px;
}
.variation {
display: none;
}
.signature-attributes {
font-size: 60%;
color: #aaa;
font-style: italic;
font-weight: lighter;
}
nav
{
display: block;
float: right;
margin-top: 28px;
width: 30%;
box-sizing: border-box;
border-left: 1px solid #ccc;
padding-left: 16px;
}
nav ul {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
font-size: 100%;
line-height: 17px;
padding: 0;
margin: 0;
list-style-type: none;
}
nav ul a, nav ul a:visited, nav ul a:active {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
line-height: 18px;
color: #4D4E53;
}
nav h3 {
margin-top: 12px;
}
nav li {
margin-top: 6px;
}
footer {
display: block;
padding: 6px;
margin-top: 12px;
font-style: italic;
font-size: 90%;
}
h1, h2, h3, h4 {
font-weight: 200;
margin: 0;
}
h1
{
font-family: 'Open Sans Light', sans-serif;
font-size: 48px;
letter-spacing: -2px;
margin: 12px 24px 20px;
}
h2, h3
{
font-size: 30px;
font-weight: 700;
letter-spacing: -1px;
margin-bottom: 12px;
}
h4
{
font-size: 18px;
letter-spacing: -0.33px;
margin-bottom: 12px;
color: #4d4e53;
}
h5, .container-overview .subsection-title
{
font-size: 120%;
font-weight: bold;
letter-spacing: -0.01em;
margin: 8px 0 3px 0;
}
h6
{
font-size: 100%;
letter-spacing: -0.01em;
margin: 6px 0 3px 0;
font-style: italic;
}
.ancestors { color: #999; }
.ancestors a
{
color: #999 !important;
text-decoration: none;
}
.clear
{
clear: both;
}
.important
{
font-weight: bold;
color: #950B02;
}
.yes-def {
text-indent: -1000px;
}
.type-signature {
color: #aaa;
}
.name, .signature {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
}
.details { margin-top: 14px; border-left: 2px solid #DDD; }
.details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; }
.details dd { margin-left: 70px; }
.details ul { margin: 0; }
.details ul { list-style-type: none; }
.details li { margin-left: 30px; padding-top: 6px; }
.details pre.prettyprint { margin: 0 }
.details .object-value { padding-top: 0; }
.description {
margin-bottom: 1em;
margin-top: 1em;
}
.code-caption
{
font-style: italic;
font-size: 107%;
margin: 0;
}
.prettyprint
{
border: 1px solid #ddd;
width: 80%;
overflow: auto;
}
.prettyprint.source {
width: inherit;
}
.prettyprint code
{
font-size: 100%;
line-height: 18px;
display: block;
padding: 4px 12px;
margin: 0;
background-color: #fff;
color: #4D4E53;
}
.prettyprint code span.line
{
display: inline-block;
}
.prettyprint.linenums
{
padding-left: 70px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.prettyprint.linenums ol
{
padding-left: 0;
}
.prettyprint.linenums li
{
border-left: 3px #ddd solid;
}
.prettyprint.linenums li.selected,
.prettyprint.linenums li.selected *
{
background-color: lightyellow;
}
.prettyprint.linenums li *
{
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.params, .props
{
border-spacing: 0;
border: 0;
border-collapse: collapse;
}
.params .name, .props .name, .name code {
color: #4D4E53;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 100%;
}
.params td, .params th, .props td, .props th
{
border: 1px solid #ddd;
margin: 0px;
text-align: left;
vertical-align: top;
padding: 4px 6px;
display: table-cell;
}
.params thead tr, .props thead tr
{
background-color: #ddd;
font-weight: bold;
}
.params .params thead tr, .props .props thead tr
{
background-color: #fff;
font-weight: bold;
}
.params th, .props th { border-right: 1px solid #aaa; }
.params thead .last, .props thead .last { border-right: 1px solid #ddd; }
.params td.description > p:first-child
{
margin-top: 0;
padding-top: 0;
}
.params td.description > p:last-child
{
margin-bottom: 0;
padding-bottom: 0;
}
.disabled {
color: #454545;
}
@@ -0,0 +1,111 @@
/* JSDoc prettify.js theme */
/* plain text */
.pln {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* string content */
.str {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a keyword */
.kwd {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a comment */
.com {
font-weight: normal;
font-style: italic;
}
/* a type name */
.typ {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* a literal value */
.lit {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* punctuation */
.pun {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* lisp open bracket */
.opn {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* lisp close bracket */
.clo {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a markup tag name */
.tag {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a markup attribute name */
.atn {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a markup attribute value */
.atv {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a declaration */
.dec {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a variable name */
.var {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* a function name */
.fun {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0;
}
@@ -0,0 +1,132 @@
/* Tomorrow Theme */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* Pretty printing styles. Used with prettify.js. */
/* SPAN elements with the classes below are added by prettyprint. */
/* plain text */
.pln {
color: #4d4d4c; }
@media screen {
/* string content */
.str {
color: #718c00; }
/* a keyword */
.kwd {
color: #8959a8; }
/* a comment */
.com {
color: #8e908c; }
/* a type name */
.typ {
color: #4271ae; }
/* a literal value */
.lit {
color: #f5871f; }
/* punctuation */
.pun {
color: #4d4d4c; }
/* lisp open bracket */
.opn {
color: #4d4d4c; }
/* lisp close bracket */
.clo {
color: #4d4d4c; }
/* a markup tag name */
.tag {
color: #c82829; }
/* a markup attribute name */
.atn {
color: #f5871f; }
/* a markup attribute value */
.atv {
color: #3e999f; }
/* a declaration */
.dec {
color: #f5871f; }
/* a variable name */
.var {
color: #c82829; }
/* a function name */
.fun {
color: #4271ae; } }
/* Use higher contrast and text-weight for printable form. */
@media print, projection {
.str {
color: #060; }
.kwd {
color: #006;
font-weight: bold; }
.com {
color: #600;
font-style: italic; }
.typ {
color: #404;
font-weight: bold; }
.lit {
color: #044; }
.pun, .opn, .clo {
color: #440; }
.tag {
color: #006;
font-weight: bold; }
.atn {
color: #404; }
.atv {
color: #060; } }
/* Style */
/*
pre.prettyprint {
background: white;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 12px;
line-height: 1.5;
border: 1px solid #ccc;
padding: 10px; }
*/
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0; }
/* IE indents via margin-left */
li.L0,
li.L1,
li.L2,
li.L3,
li.L4,
li.L5,
li.L6,
li.L7,
li.L8,
li.L9 {
/* */ }
/* Alternate shading for lines */
li.L1,
li.L3,
li.L5,
li.L7,
li.L9 {
/* */ }
@@ -0,0 +1,10 @@
<?js
var data = obj;
var self = this;
?>
<?js if (data.augments && data.augments.length) { ?>
<ul><?js data.augments.forEach(function(a) { ?>
<li><?js= self.linkto(a, a) ?></li>
<?js }) ?></ul>
<?js } ?>

Some files were not shown because too many files have changed in this diff Show More