mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-11-04 00:08:00 +00:00
GEEKING
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -0,0 +1,135 @@
|
||||
JSDoc 3
|
||||
=======
|
||||
[](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.
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
})();
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"tags": {
|
||||
"allowUnknownTags": true
|
||||
},
|
||||
"source": {
|
||||
"includePattern": ".+\\.js(doc)?$",
|
||||
"excludePattern": "(^|\\/|\\\\)_"
|
||||
},
|
||||
"plugins": [],
|
||||
"templates": {
|
||||
"cleverLinks": false,
|
||||
"monospaceLinks": false,
|
||||
"default": {
|
||||
"outputSourceFiles": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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];
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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ł 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, '<')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(/'/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, '&')
|
||||
.replace(/</g, '<');
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
@@ -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];
|
||||
});
|
||||
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.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, '');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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 <, & 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 = '…' + 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( ' → %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);
|
||||
};
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 116 KiB |
Binary file not shown.
BIN
Binary file not shown.
+1830
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 118 KiB |
BIN
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 120 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 114 KiB |
Binary file not shown.
BIN
Binary file not shown.
+1835
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 120 KiB |
BIN
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 117 KiB |
Binary file not shown.
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
+202
@@ -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
Reference in New Issue
Block a user