新增调试信息
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
**/*
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "jsdoc 4",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"program": "node jsdoc.js -c ./tmp/jsdoc-conf.js ./tmp/foo.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
+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.
|
||||
+899
@@ -0,0 +1,899 @@
|
||||
# JSDoc change history
|
||||
|
||||
This file describes notable changes in each version of JSDoc, starting with version 3.0.0.
|
||||
|
||||
|
||||
## 4.0.3 (May 2024)
|
||||
|
||||
Updates dependencies.
|
||||
|
||||
|
||||
## 4.0.2 (February 2023)
|
||||
|
||||
Updates the Babel parser, and enables additional Babel plugins for ECMAScript proposals.
|
||||
|
||||
|
||||
## 4.0.1 (February 2023)
|
||||
|
||||
If an inline link tag uses a pipe delimiter, and there are spaces around the pipe delimiter, then
|
||||
the HTML link no longer contains `%20` at the end; also, the link text no longer contains extra
|
||||
spaces. For example, `{@link https://example.com | link text}` is now rendered as
|
||||
`<a href="https://example.com/">link text</a>` rather than
|
||||
`<a href="https://example.com/%20"> link text</a>`.
|
||||
|
||||
|
||||
## 4.0.0 (November 2022)
|
||||
|
||||
+ JSDoc releases now use [semantic versioning](https://semver.org/). If JSDoc makes
|
||||
backwards-incompatible changes in the future, the major version will be incremented.
|
||||
+ JSDoc no longer uses the [`taffydb`](https://taffydb.com/) package. If your JSDoc template or
|
||||
plugin uses the `taffydb` package, see the
|
||||
[instructions for replacing `taffydb` with `@jsdoc/salty`](https://github.com/jsdoc/jsdoc/tree/main/packages/jsdoc-salty#use-salty-in-a-jsdoc-template).
|
||||
+ JSDoc now supports Node.js 12.0.0 and later.
|
||||
|
||||
|
||||
## 3.6.11 (July 2022)
|
||||
|
||||
Updates dependency versions to make JSDoc compatible with Node.js 12.0.0 and later.
|
||||
|
||||
|
||||
## 3.6.10 (January 2022)
|
||||
|
||||
Fixes an issue in JSDoc 3.6.9 that prevented JSDoc from being installed in some continuous
|
||||
integration (CI) environments.
|
||||
|
||||
|
||||
## 3.6.9 (January 2022)
|
||||
|
||||
Fixes an issue in JSDoc 3.6.8 that prevented `npm install jsdoc` from working.
|
||||
|
||||
|
||||
## 3.6.8 (January 2022)
|
||||
|
||||
Updates dependencies.
|
||||
|
||||
|
||||
## 3.6.7 (May 2021)
|
||||
|
||||
Updates dependencies.
|
||||
|
||||
|
||||
## 3.6.6 (September 2020)
|
||||
|
||||
Fixes an issue that could cause members of an interface to be tracked incorrectly if the interface
|
||||
was both defined as an ES2015 class and assigned to a variable. For example:
|
||||
|
||||
```js
|
||||
/** @interface */
|
||||
foo.Bar = class {
|
||||
constructor() {
|
||||
/** This member was missing from the generated docs. */
|
||||
this.baz = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 3.6.5 (July 2020)
|
||||
|
||||
Prevents circular references in doclets when two function parameters use the same type expression,
|
||||
and the `--debug` flag is present.
|
||||
|
||||
|
||||
## 3.6.4 (April 2020)
|
||||
|
||||
Updates dependencies.
|
||||
|
||||
|
||||
## 3.6.3 (July 2019)
|
||||
|
||||
Updates dependencies.
|
||||
|
||||
|
||||
## 3.6.2 (May 2019)
|
||||
|
||||
Fixes an issue that prevented ES 2015 classes from appearing in generated docs. ([#1644][1644])
|
||||
|
||||
|
||||
## 3.6.1 (May 2019)
|
||||
|
||||
Prevents a crash when using type applications in Node.js 12. ([#1643][1643])
|
||||
|
||||
|
||||
## 3.6.0 (May 2019)
|
||||
|
||||
### Major changes
|
||||
|
||||
+ JSDoc is now compatible with Node.js 12, and it requires Node.js 8.15.0 or later.
|
||||
+ JSDoc now recognizes all documented Closure Compiler tags. ([#605][605])
|
||||
|
||||
### Enhancements
|
||||
|
||||
+ You can now use the `templates.useShortNamesInLinks` configuration setting to show the short name
|
||||
of each symbol in link text (for example, `baz`), rather than the full longname (for example,
|
||||
`foo.bar.baz`). ([#738][738])
|
||||
+ When you enable the Markdown plugin, you can now specify a function that performs syntax
|
||||
highlighting in code blocks. ([#1412][1412])
|
||||
+ The default template now places namespaces near the top of the TOC. ([#1410][1410])
|
||||
|
||||
### Bug fixes
|
||||
|
||||
+ When you add a JSDoc comment to an ES2015 constructor, JSDoc now preserves all of the JSDoc tags,
|
||||
not only the description and parameters. ([#1129][1129])
|
||||
+ The `@exports` tag now works correctly when it is combined with the `@enum` tag. ([#970][970])
|
||||
+ When you enable the Markdown plugin, and you use a code fence with the language set to `plain`,
|
||||
JSDoc no longer pretty-prints the code block in the generated documentation. ([#1361][1361])
|
||||
|
||||
|
||||
## 3.5.5 (September 2017)
|
||||
|
||||
Fixes a compatibility issue with Node.js 8.5.0. (#1438)
|
||||
|
||||
|
||||
## 3.5.4 (August 2017)
|
||||
|
||||
+ When a class uses the `@hideconstructor` tag, the default template no longer displays the names of
|
||||
parameters that the constructor accepts. (#1397)
|
||||
+ When an arrow function expression returns a class, the class's methods and properties are now
|
||||
named correctly. (#1409)
|
||||
+ JSDoc no longer crashes when an anonymous class is passed as a function parameter. (#1416)
|
||||
+ JSDoc now allows `import` and `export` declarations anywhere where a statement is allowed. (#1411)
|
||||
+ JSDoc now allows `return` statements outside of functions. (#1411)
|
||||
+ JSDoc now allows `super()` calls outside of a method definition. (#1411)
|
||||
+ JSDoc no longer exits before the `STDOUT` pipe has been flushed. (#1408)
|
||||
|
||||
|
||||
## 3.5.3 (July 2017)
|
||||
|
||||
+ Non-JSDoc comments (comments that do not begin with `/**`) are now ignored. (#1398)
|
||||
+ JSDoc no longer crashes when it parses a class property with no value assigned to it. (#1400)
|
||||
+ When there are JSDoc comments at the end of a source file that has a `'use strict';` directive,
|
||||
the comments are no longer ignored. (#1396)
|
||||
+ Namepaths that contain an `@` sign (for example, `module:@prefix/my-module~myCallback`) are now
|
||||
parsed correctly. (#1302)
|
||||
+ The default template now displays interfaces that belong to a namespace. (#1406)
|
||||
+ When an ES2015 class inside a module uses an `@alias` tag, the class's constructor now gets the
|
||||
correct longname. (#1395)
|
||||
+ When there are no input files to process, JSDoc no longer prints help text to the console. (#1404)
|
||||
|
||||
|
||||
## 3.5.2 (July 2017)
|
||||
|
||||
+ The default template now hides parameters and properties for class constructors that are hidden
|
||||
with the `@hideconstructor` tag. (#1397)
|
||||
+ JSDoc now uses an improved algorithm for locating plugins and template resources. (#1394)
|
||||
+ When the `@alias` tag identifies an instance member (for example, `@alias Foo#bar`), the alias is
|
||||
now applied correctly. (#1385)
|
||||
+ When the `@alias` tag is applied to a class that is within a module and is aliased to the module
|
||||
name, the class's instance members are now documented correctly. (#1134)
|
||||
+ Fixed a crash when a `@param` tag uses the wrong delimiter to close the type expression (for
|
||||
example, `@param {Object)`). (#1221)
|
||||
+ The Markdown plugin now converts Markdown-formatted text in the `@summary` tag. (#1149)
|
||||
|
||||
|
||||
## 3.5.1 (July 2017)
|
||||
|
||||
+ Fixed an issue that prevented JSDoc from working on versions of Node.js prior to 5.10.0. (#1393)
|
||||
+ If the JSDoc configuration file does not have a file extension, JSDoc now assumes that the file
|
||||
is in JSON format. (#1391)
|
||||
|
||||
|
||||
## 3.5.0 (July 2017)
|
||||
|
||||
### Major changes
|
||||
|
||||
+ JSDoc now uses the [Babylon](https://github.com/babel/babylon) JavaScript parser, which means that
|
||||
JSDoc can parse any JavaScript or JSX file that is supported by the [Babel](https://babeljs.io/)
|
||||
compiler. For example, JSDoc can now parse JavaScript files that include all of the following
|
||||
language features:
|
||||
|
||||
+ [Decorators](https://github.com/tc39/proposal-decorators)
|
||||
+ [Public and private class fields](https://github.com/tc39/proposal-class-fields)
|
||||
+ [Asynchronous iterators](https://github.com/tc39/proposal-async-iteration)
|
||||
+ [Dynamic `import()`](https://github.com/tc39/proposal-dynamic-import)
|
||||
+ [Optional chaining](https://github.com/tc39/proposal-optional-chaining)
|
||||
|
||||
+ You can now use a JavaScript file to configure JSDoc. The JavaScript file must be a CommonJS
|
||||
module that exports a single configuration object. See the
|
||||
[documentation](https://jsdoc.app/about-configuring-jsdoc.html) for details and examples.
|
||||
+ Fixed multiple issues with documenting ES2015 classes and modules. See "Bug fixes" for details.
|
||||
+ JSDoc now requires Node.js 4.2.0 or later.
|
||||
|
||||
### New tags
|
||||
|
||||
**Note**: Third-party templates may not support these new tags.
|
||||
|
||||
+ You can now use the new [`@async` tag](https://jsdoc.app/tags-async.html) to indicate that a
|
||||
function is asynchronous (that is, that it was declared with the syntax `async function foo() {}`).
|
||||
In general, you do not need to use this tag, because JSDoc autodetects asynchronous functions.
|
||||
(#1188)
|
||||
+ You can now use the new [`@generator` tag](https://jsdoc.app/tags-generator.html) to indicate
|
||||
that a function is a generator function. In general, you do not need to use this tag, because JSDoc
|
||||
autodetects generator functions. (#1158)
|
||||
+ You can now use the new [`@hideconstructor` tag](https://jsdoc.app/tags-hideconstructor.html) to
|
||||
tell JSDoc to hide a class's constructor from the documentation. (#952)
|
||||
+ You can now use the new [`@package` tag](https://jsdoc.app/tags-package.html) to indicate that a
|
||||
symbol is package-private. (#962)
|
||||
+ You can now use the new [`@yields` tag](https://jsdoc.app/tags-yields.html) to document the
|
||||
value that is yielded by a generator function. (#1388)
|
||||
|
||||
### Enhancements
|
||||
|
||||
+ JSDoc can now parse files that contain asynchronous functions (that is, functions declared as
|
||||
`async function foo() {}`), and JSDoc autodetects when a function is asynchronous. (#1188)
|
||||
+ JSDoc now autodetects generator functions. (#1158)
|
||||
+ When JSDoc cannot parse a type expression, it now logs the line number on which the type
|
||||
expression was found. (#1057)
|
||||
+ When JSDoc fires `jsdocCommentFound` and `symbolFound` events, the event now includes a `columnno`
|
||||
property indicating the column number on which the comment or symbol was found. (#1362)
|
||||
+ You can now use the new `sourceType` configuration option to control how JavaScript files are
|
||||
parsed. The default value is `module`. Set the value to `script` to suppress implied strict mode;
|
||||
this setting will also prevent you from using ES2015 modules. (#1210)
|
||||
+ You can now use the new `recurseDepth` configuration option to control how many levels deep JSDoc
|
||||
will recursively search for files. The default value is 10. (#1340)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
+ JSDoc now correctly documents the constructors and instance properties of ES2015 classes. (#1182)
|
||||
+ JSDoc now correctly documents the constructor of an ES2015 class exported from an ES2015 module.
|
||||
(#1272)
|
||||
+ JSDoc now uses the correct scope for exported symbols, and their children, in ES2015 modules.
|
||||
(#1293)
|
||||
+ When JSDoc is run in a directory that has a `plugins/` or `templates/` directory, JSDoc can now
|
||||
discover plugins and templates in other directories. (#1081, #1308)
|
||||
+ JSDoc no longer crashes when it reads a UTF-8 JSON file with a leading BOM. (#1256, #1297)
|
||||
+ When a function is assigned to a variable, JSDoc now autodetects the function's default and
|
||||
repeatable parameters. (#1054)
|
||||
+ JSDoc no longer crashes when the `@author` tag does not have a value. (#1289)
|
||||
+ JSDoc now always calls `process.exit()` when exiting. (#1287)
|
||||
|
||||
### Default template
|
||||
|
||||
+ The default template now identifies asynchronous and generator functions. (#1158, #1188)
|
||||
+ The default template now displays appropriate documentation for namespaces that are also
|
||||
functions. (#955)
|
||||
+ Images that are wider than the text area are now displayed correctly. (#1359)
|
||||
|
||||
|
||||
|
||||
## 3.4.3 (November 2016)
|
||||
|
||||
Updated JSDoc's `LICENSE.md` file.
|
||||
|
||||
|
||||
## 3.4.2 (October 2016)
|
||||
|
||||
+ Classes exported from an ES2015 module are now documented correctly. (#1137)
|
||||
+ Fixed an issue that prevented plugins and templates from being loaded correctly. (#1259)
|
||||
+ Fixed a crash when using the experimental object spread operator in assignments. (#1258)
|
||||
|
||||
|
||||
## 3.4.1 (September 2016)
|
||||
|
||||
### Enhancements
|
||||
|
||||
+ When installing JSDoc from NPM, all dependencies are now pulled from NPM. (#961)
|
||||
+ The configuration setting `tags.allowUnknownTags` may now contain an array of tag names that
|
||||
should be allowed. (#1159)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
+ When an ES2015 module's default export is a class, JSDoc now documents the class correctly.
|
||||
(#1113, #1120)
|
||||
+ JSDoc no longer crashes when an ES2015 module exports an anonymous class. (#1113)
|
||||
+ JSDoc no longer crashes when the experimental object spread operator is used. (#1141)
|
||||
+ In ES2015 methods, JSDoc now autodetects whether a parameter is a default or repeatable parameter.
|
||||
(#1144)
|
||||
+ The Markdown plugin now works correctly with inline tags that contain special characters, such as
|
||||
`{@link chat."#channel"}`. (#1035)
|
||||
+ When JSDoc is run in a directory that has a `plugins/` or `templates/` directory, JSDoc can now
|
||||
discover plugins and templates in other directories. (#1081)
|
||||
|
||||
### Templates
|
||||
|
||||
+ The default template now uses appropriate styles for displaying tables. (#1064)
|
||||
+ The default template's CSS file no longer uses the same style for both `<h2>` and `<h3>` elements.
|
||||
(#1108)
|
||||
+ JSDoc now includes a `silent` template that generates no output. This template makes it easier to
|
||||
use JSDoc as a linter to check for syntax errors and unrecognized tags in documentation comments.
|
||||
(#1160)
|
||||
|
||||
|
||||
## 3.4.0 (November 2015)
|
||||
|
||||
### Major changes
|
||||
|
||||
+ JSDoc is now compatible with Node.js 4.0.0 and later.
|
||||
+ JSDoc no longer runs on Mozilla Rhino. Use Node.js to run JSDoc.
|
||||
+ JSDoc can now parse ECMAScript 2015 code, including code that uses native classes and modules.
|
||||
(#555)
|
||||
+ JSDoc can now parse [JSX](https://facebook.github.io/jsx/) files. (#1001)
|
||||
+ JSDoc's `app` and `env` global variables are now deprecated. Avoid using the `app` global. Use the
|
||||
`jsdoc/env` module instead of the `env` global. (#812)
|
||||
|
||||
### Enhancements
|
||||
|
||||
+ `const` declarations are now automatically treated as constants. (#555)
|
||||
+ Templates may now run asynchronously. To create an asynchronous template, simply return a promise
|
||||
from your template's `publish` method. (#953)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
+ Symbols now get the correct longname when they are defined as properties of a prototype and
|
||||
include special characters, such as `#` and `.` (for example, `Foo.prototype['this#is#bar']`).
|
||||
(#888)
|
||||
+ Instance members that are defined as computed properties of `this` (for example, `this['bar']`)
|
||||
now get the correct longname. (#890)
|
||||
+ When an instance member (for example, `this.bar`) is documented within a member of a prototype
|
||||
(for example, `Foo.prototype.setBar`), the instance member's longname is now set correctly. (#1011)
|
||||
+ The `@borrows` tag now works with symbol names that contain whitespace. (#818)
|
||||
|
||||
### Plugins
|
||||
|
||||
+ For the Markdown plugin, you can now autogenerate a heading ID for each heading by setting the
|
||||
configuration property `markdown.idInHeadings` to `true`. (#1032)
|
||||
|
||||
### Template improvements
|
||||
|
||||
+ In the default template, you can now show the full namepath of each object in the navigation
|
||||
column by setting the configuration property `templates.default.useLongnameInNav` to `true`. (#986)
|
||||
+ In the Haruki template, falsy default values now appear in the output. (#1063)
|
||||
|
||||
|
||||
## 3.3.3 (September 2015)
|
||||
|
||||
+ Symbols named `prototype` are now handled correctly. (#891)
|
||||
+ Fixed an issue that could cause JSDoc to go into an infinite loop when a module is documented
|
||||
twice. (#975)
|
||||
+ Fixed an issue that could cause parsing errors on valid regular expressions. (#1053)
|
||||
|
||||
|
||||
## 3.3.2 (June 2015)
|
||||
|
||||
JSDoc no longer crashes when parsing a large number of files, or a single object that has a large
|
||||
number of properties. (#976)
|
||||
|
||||
|
||||
## 3.3.1 (June 2015)
|
||||
|
||||
+ Fixed a crash in the Haruki template. (#1005)
|
||||
+ When a type expression includes a record type with numeric keys (for example, `{0: string}`), the
|
||||
type expression is now parsed correctly. (#1016)
|
||||
|
||||
|
||||
## 3.3.0 (May 2015)
|
||||
|
||||
### Major changes
|
||||
|
||||
+ You can now run JSDoc on Node.js. (#93)
|
||||
+ You can now use the `@interface` and `@implements` tags to document interfaces and their
|
||||
implementations. (#720, #828)
|
||||
+ Closure Compiler's `@inheritDoc` and `@override` tags are now supported. (#53)
|
||||
+ If the JSDoc comment for a symbol includes the `@mixes` tag, all of the mixins now appear in the
|
||||
symbol's documentation. (#378)
|
||||
+ JSDoc can now log information to the console as it runs (for example, the name of each file that
|
||||
JSDoc parses). To log this information, run JSDoc with the `--verbose` flag. (#416)
|
||||
+ You can now use any file as the package or README file for your documentation. Use the
|
||||
`-P/--package/` and `-R/--readme` flags to specify the package and README file. (#708)
|
||||
+ The default template's typography and color scheme have been significantly improved. (#550, #780,
|
||||
#843)
|
||||
|
||||
### Enhancements
|
||||
|
||||
+ You can now use the `--pedantic` flag to treat all errors as fatal errors, and to treat warnings
|
||||
as errors. This flag replaces the `--lenient` flag, which had roughly the opposite meaning and is no
|
||||
longer available. (#416)
|
||||
+ You can now use the `-a/--access` flag to control whether private, protected, and public symbols
|
||||
appear in the documentation. (#860, #861)
|
||||
+ You can now use the `--debug` flag to log detailed debugging information to the console. This
|
||||
information can help you diagnose bugs in JSDoc itself. (#416)
|
||||
+ JSDoc's configuration file can now contain JavaScript comments. (#660)
|
||||
+ You can now include source files from a directory, but exclude one of its subdirectories, by
|
||||
adding the subdirectory to the `source.exclude` option in the configuration file. (#484)
|
||||
+ The `source.exclude` option now works correctly when JSDoc is run with the `-r/--recurse` flag.
|
||||
(#616)
|
||||
+ When JSDoc is run with the `-r/--recurse` flag, it now scans for tutorials recursively. (#712)
|
||||
+ JSDoc's `-X/--explain` option now runs much more quickly. (#633)
|
||||
+ If all of the text for an `@example` tag is indented, JSDoc now removes the extra indentation.
|
||||
(#540)
|
||||
+ The default value for a parameter or property can now include brackets (for example,
|
||||
`@param {Array.<string>} [foo=['bar']]`). (#640)
|
||||
+ You can now provide a default value for parameters and properties that are not optional (for
|
||||
example, `@property {string} foo='bar'`). (#791)
|
||||
+ If the `@type` tag includes a description (for example, `@type {string} some text`), JSDoc now
|
||||
parses the type expression correctly and discards the description. (#615)
|
||||
+ You can now add JSDoc comments to function parameters. (#473)
|
||||
+ For Closure Compiler projects, you can now enable a Closure Compiler-specific tag dictionary that
|
||||
more closely matches Closure Compiler's semantics. To enable Closure Compiler semantics, set the
|
||||
configuration file's `tags.dictionaries` option to `['closure']`. This option can contain the values
|
||||
`jsdoc`, `closure`, or both. If multiple dictionaries are enabled, and a tag is defined in more than
|
||||
one dictionary, JSDoc uses the definition from the first dictionary that contains the tag. (#729,
|
||||
#730, #731, #732)
|
||||
+ If one symbol overrides another, JSDoc now adds an `overrides` property to the doclet that is
|
||||
overriding another. The `overrides` property contains the longname of the overridden symbol. (#792)
|
||||
+ When a JSDoc tag contains a type expression, the doclet's `type` object now includes a hidden
|
||||
`parsedType` property. The `parsedType` property contains a syntax tree that represents the type
|
||||
expression. The syntax tree is generated by [Catharsis](https://github.com/hegemonic/catharsis), and
|
||||
its format may change in the future. (#576)
|
||||
+ JSDoc now allows output filenames to contain non-ASCII characters. In addition, links to output
|
||||
files are now URL-encoded when necessary. (#677)
|
||||
+ JSDoc now ensures that output filenames do not have a leading underscore. (#758)
|
||||
+ JSDoc now tries to ensure that `id` attributes in output files are unique within that file. (#539)
|
||||
+ JSDoc now has an up-to-date JSON Schema file for parse results. The schema file is available in
|
||||
`lib/jsdoc/schema.js`. (#327)
|
||||
+ JSDoc now extracts more information from package files. (#710)
|
||||
+ JSDoc now displays usage information if you run JSDoc without any input files, or with an
|
||||
unrecognized command-line option. (#609, #840)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
+ When the `allowUnknownTags` option is set to `false`, JSDoc no longer logs warnings about the
|
||||
presence of `@also` and `@scope` tags. (#574)
|
||||
+ Fixed several errors when parsing type expressions. (#619, #644, #652, #705, #767)
|
||||
+ Properties added to the `module.exports` object can now be documented. (#500)
|
||||
+ When a symbol's name starts with the same characters as its parent namespace (such as `Vector` in
|
||||
`V.Vector`), JSDoc now assigns the correct longname to the symbol. (#608)
|
||||
+ If a child class inherits from multiple parent classes, and the parent classes have instance
|
||||
members with the same name, the child class no longer displays the documentation from both parent
|
||||
classes. (#613)
|
||||
+ If a source file contains an object literal, and one of the property names is a whitespace
|
||||
character or a character that must be escaped in a regular expression, JSDoc now parses the file
|
||||
successfully. (#549, #775)
|
||||
+ Virtual comments now work correctly for overloaded functions. (#727)
|
||||
+ When a virtual comment appears within a module, JSDoc now assigns the correct values to the
|
||||
virtual comment doclet's `memberof`, `longname`, and `scope` properties. (#631)
|
||||
+ JSDoc now sets the `scope` property to `global` for all global doclets. (#684)
|
||||
+ Module doclets no longer have a `scope` property. (#782)
|
||||
+ In Markdown tutorials, JSDoc no longer unescapes HTML entities. (#743)
|
||||
+ If a longname includes a variation (for example, `Foo#bar(variation)`), the link text is now
|
||||
preserved when generating HTML links. (#857)
|
||||
+ When a single JSDoc comment includes `@class`, `@classdesc`, and `@constructor` tags, JSDoc no
|
||||
longer ignores the value of the `@classdesc` tag. (#806)
|
||||
+ For tags where the name and type are both optional (`@constant`, `@external`, `@member`,
|
||||
`@module`, `@namespace`, and `@param`), JSDoc now parses the tag correctly when it includes a type
|
||||
but not a name. (#351, #535)
|
||||
+ The `@default` tag now works correctly when used with an array literal. (#604)
|
||||
+ The `@enum` tag now works correctly when the enumeration is part of a chain of assignments (for
|
||||
example, `var FOO = exports.FOO = {/* enumerated values */}`). (#702)
|
||||
+ The `@exports` and `@module` tags now work correctly when their value includes a `module:`
|
||||
namespace (for example, `@exports module:foo`). (#786)
|
||||
+ The `@memberof` tag now works correctly when it refers to a module that is defined in a separate
|
||||
file. (#880)
|
||||
+ The `@variation` tag now works correctly when its value is enclosed in parentheses (for example,
|
||||
`@variation (foo)`). (#850)
|
||||
|
||||
### Plugins
|
||||
|
||||
+ Tag definitions can now have a `mustNotHaveDescription` property. When this property is set to
|
||||
`true`, JSDoc will warn the user if the tag text includes a description (such as `The description`
|
||||
in `@param {string} foo - The description`). (#615)
|
||||
+ Tag definitions can now call the method `dictionary.normalize`, which is a synonym for
|
||||
`dictionary.normalise`. (#884)
|
||||
+ The Markdown plugin no longer prevents inline `{@link}` tags from working. (#518)
|
||||
+ The Markdown plugin now converts `@author` and `@throws` tag values to HTML by default. (#736,
|
||||
#878)
|
||||
+ JSDoc now includes a `summarize` plugin that automatically generates summaries based on the
|
||||
description. (#485)
|
||||
+ JSDoc now includes an `underscore` plugin that finds symbols whose names begin with an underscore
|
||||
and automatically tags them as `@private`. (#471)
|
||||
+ Plugins can now replace the `doclet` property of `newDoclet` events. (#584)
|
||||
|
||||
### Template improvements
|
||||
|
||||
+ You can now override the default template's main layout file, `layout.tmpl`, by setting the
|
||||
`templates.default.layoutFile` option in JSDoc's configuration file. The property can contain a
|
||||
relative or absolute path to the replacement for `layout.tmpl`. Relative paths are resolved against
|
||||
the path to the configuration file; the current working directory; and the JSDoc directory, in that
|
||||
order. (#480)
|
||||
+ When the `templates.default.outputSourceFiles` option is set to `false`, the documentation no
|
||||
longer shows the path to each source file. (#571)
|
||||
+ You can now use the property `templates.default.staticFiles.include` to list files that will be
|
||||
copied to the output directory. For backwards compatibility, the property
|
||||
`templates.default.staticFiles.paths` is also supported but is deprecated. (#785)
|
||||
+ The property `templates.default.staticFiles.include` now works correctly when an absolute path is
|
||||
specified. (#939)
|
||||
+ The `templates.default.staticFiles` options now work correctly on Windows. (#785)
|
||||
+ In output files, you can now prevent the date from appearing in the footer by setting the property
|
||||
`templates.default.includeDate` to `false`. (#910)
|
||||
+ Output files no longer show the default value for members of an enumeration. (#689)
|
||||
+ In certain types of AMD modules, the module-overview section is no longer duplicated. (#853)
|
||||
+ If a constructor is assigned to `module.exports`, the value of the `@classdesc` tag now appears in
|
||||
the documentation. (#740)
|
||||
+ If a constructor is assigned to `module.exports`, and the constructor inherits from another class,
|
||||
the parent class is now listed in the documentation. (#594)
|
||||
+ Text within an `@example` tag, including HTML tags, is now properly escaped. (#511)
|
||||
+ If a member has a `@fires` tag, the tag information now appears in the documentation. (#568)
|
||||
+ If a symbol has members that use the `@mixin` tag, the mixins are now listed in the documentation.
|
||||
(#379, #602)
|
||||
+ When multiple `@param` tags are used to document properties of array values (for example,
|
||||
`@param {Object[]} foo` and `@param {string} foo[].bar`), the properties are now grouped into the
|
||||
appropriate row of the parameters table. (#870)
|
||||
+ If a member has a `@requires` tag, the tag information now appears in the documentation. (#563)
|
||||
+ Type expressions are now presented more clearly. (#618)
|
||||
+ Pretty-printed source files now include line numbers. (#532)
|
||||
+ When you run JSDoc with a single input file, the full path to the file no longer appears in the
|
||||
documentation. (#553)
|
||||
+ When an overloaded function is assigned to `module.exports`, the documentation now displays all of
|
||||
the signatures for the overloaded function. (#727)
|
||||
+ Resolved several issues that caused the default template to generate invalid HTML. (#843)
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
[605]: https://github.com/jsdoc/jsdoc/issues/605
|
||||
[738]: https://github.com/jsdoc/jsdoc/issues/738
|
||||
[970]: https://github.com/jsdoc/jsdoc/issues/970
|
||||
[1129]: https://github.com/jsdoc/jsdoc/issues/1129
|
||||
[1361]: https://github.com/jsdoc/jsdoc/issues/1361
|
||||
[1410]: https://github.com/jsdoc/jsdoc/issues/1410
|
||||
[1412]: https://github.com/jsdoc/jsdoc/issues/1412
|
||||
[1643]: https://github.com/jsdoc/jsdoc/issues/1643
|
||||
[1644]: https://github.com/jsdoc/jsdoc/issues/1644
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
# License
|
||||
|
||||
JSDoc 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-present Michael Mathews <micmath@gmail.com> and the
|
||||
[contributors to JSDoc](https://github.com/jsdoc/jsdoc/graphs/contributors). All
|
||||
rights reserved.
|
||||
|
||||
You can obtain a copy of the License at:
|
||||
https://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 is available at: https://github.com/jsdoc/jsdoc
|
||||
|
||||
# Third-party software
|
||||
|
||||
JSDoc includes 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.
|
||||
|
||||
## 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
|
||||
|
||||
## 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:
|
||||
https://fonts.google.com/specimen/Open+Sans
|
||||
|
||||
## Tomorrow theme for Google Code Prettify
|
||||
|
||||
The Tomorrow theme for Google Code Prettify is distributed under the MIT
|
||||
license, which is reproduced above.
|
||||
|
||||
Copyright (c) 2016 Yoshihide Jimbo.
|
||||
|
||||
The source code for the Tomorrow theme is available at:
|
||||
https://github.com/jmblog/color-themes-for-google-code-prettify
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
# JSDoc
|
||||
|
||||
[](http://travis-ci.org/jsdoc/jsdoc)
|
||||
|
||||
An API documentation generator for JavaScript.
|
||||
|
||||
Want to contribute to JSDoc? Please read `CONTRIBUTING.md`.
|
||||
|
||||
Installation and Usage
|
||||
----------------------
|
||||
|
||||
JSDoc supports stable versions of Node.js 12.0.0 and later. You can install
|
||||
JSDoc globally or in your project's `node_modules` folder.
|
||||
|
||||
To install the latest version on npm globally (might require `sudo`;
|
||||
[learn how to fix this](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally)):
|
||||
|
||||
npm install -g jsdoc
|
||||
|
||||
To install the latest version on npm locally and save it in your package's
|
||||
`package.json` file:
|
||||
|
||||
npm install --save-dev jsdoc
|
||||
|
||||
To install the latest development version locally, without updating your
|
||||
project's `package.json` file:
|
||||
|
||||
npm install git+https://github.com/jsdoc/jsdoc.git
|
||||
|
||||
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
|
||||
|
||||
If you installed JSDoc globally, 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.
|
||||
|
||||
## Templates and tools
|
||||
|
||||
The JSDoc community has created templates and other tools to help you generate
|
||||
and customize your documentation. Here are a few of them:
|
||||
|
||||
### Templates
|
||||
|
||||
+ [jaguarjs-jsdoc](https://github.com/davidshimjs/jaguarjs-jsdoc)
|
||||
+ [DocStrap](https://github.com/docstrap/docstrap)
|
||||
([example](https://docstrap.github.io/docstrap))
|
||||
+ [jsdoc3Template](https://github.com/DBCDK/jsdoc3Template)
|
||||
([example](https://github.com/danyg/jsdoc3Template/wiki#wiki-screenshots))
|
||||
+ [minami](https://github.com/Nijikokun/minami)
|
||||
+ [docdash](https://github.com/clenemt/docdash)
|
||||
([example](http://clenemt.github.io/docdash/))
|
||||
+ [tui-jsdoc-template](https://github.com/nhnent/tui.jsdoc-template)
|
||||
([example](https://nhnent.github.io/tui.jsdoc-template/latest/))
|
||||
+ [better-docs](https://github.com/SoftwareBrothers/better-docs)
|
||||
([example](https://softwarebrothers.github.io/admin-bro-dev/index.html))
|
||||
|
||||
### Build tools
|
||||
|
||||
+ [JSDoc Grunt plugin](https://github.com/krampstudio/grunt-jsdoc)
|
||||
+ [JSDoc Gulp plugin](https://github.com/mlucool/gulp-jsdoc3)
|
||||
|
||||
### Other tools
|
||||
|
||||
+ [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown)
|
||||
+ [Integrating GitBook with
|
||||
JSDoc](https://medium.com/@kevinast/integrate-gitbook-jsdoc-974be8df6fb3)
|
||||
|
||||
## For more information
|
||||
|
||||
+ Documentation is available at [jsdoc.app](https://jsdoc.app/).
|
||||
+ Contribute to the docs at
|
||||
[jsdoc/jsdoc.github.io](https://github.com/jsdoc/jsdoc.github.io).
|
||||
+ [Join JSDoc's Slack channel](https://jsdoc-slack.appspot.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 is copyright (c) 2011-present Michael Mathews <micmath@gmail.com> and the
|
||||
[contributors to JSDoc](https://github.com/jsdoc/jsdoc/graphs/contributors).
|
||||
|
||||
JSDoc is free software, licensed under the Apache License, Version 2.0. See the
|
||||
file `LICENSE.md` in this distribution for more details.
|
||||
+465
@@ -0,0 +1,465 @@
|
||||
/* eslint-disable indent, no-process-exit */
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
module.exports = (() => {
|
||||
const app = require('jsdoc/app');
|
||||
const env = require('jsdoc/env');
|
||||
const logger = require('jsdoc/util/logger');
|
||||
const stripBom = require('jsdoc/util/stripbom');
|
||||
const stripJsonComments = require('strip-json-comments');
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const props = {
|
||||
docs: [],
|
||||
packageJson: null,
|
||||
shouldExitWithError: false,
|
||||
tmpdir: null
|
||||
};
|
||||
|
||||
const FATAL_ERROR_MESSAGE = 'Exiting JSDoc because an error occurred. See the previous log ' +
|
||||
'messages for details.';
|
||||
const cli = {};
|
||||
|
||||
// TODO: docs
|
||||
cli.setVersionInfo = () => {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// allow this to throw--something is really wrong if we can't read our own package file
|
||||
const info = JSON.parse( stripBom.strip(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 = () => {
|
||||
const _ = require('underscore');
|
||||
const args = require('jsdoc/opts/args');
|
||||
const Config = require('jsdoc/config');
|
||||
let config;
|
||||
const fs = require('jsdoc/fs');
|
||||
const path = require('jsdoc/path');
|
||||
|
||||
let confPath;
|
||||
let isFile;
|
||||
|
||||
const defaultOpts = {
|
||||
destination: './out/',
|
||||
encoding: 'utf8'
|
||||
};
|
||||
|
||||
try {
|
||||
env.opts = args.parse(env.args);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`${e.message}\n`);
|
||||
cli.printHelp().then(() => {
|
||||
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 {
|
||||
switch ( path.extname(confPath) ) {
|
||||
case '.js':
|
||||
config = require( path.resolve(confPath) ) || {};
|
||||
break;
|
||||
case '.json':
|
||||
case '.EXAMPLE':
|
||||
default:
|
||||
config = fs.readFileSync(confPath, 'utf8');
|
||||
break;
|
||||
}
|
||||
env.conf = new Config(config).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 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 = () => {
|
||||
logger.debug( cli.getVersion() );
|
||||
|
||||
logger.debug('Environment info: %j', {
|
||||
env: {
|
||||
conf: env.conf,
|
||||
opts: env.opts
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
cli.logFinish = () => {
|
||||
let delta;
|
||||
let 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 = cb => {
|
||||
let cmd;
|
||||
|
||||
const opts = env.opts;
|
||||
|
||||
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().then(errorCode => {
|
||||
if (!errorCode && props.shouldExitWithError) {
|
||||
errorCode = 1;
|
||||
}
|
||||
cb(errorCode);
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
cli.printHelp = () => {
|
||||
cli.printVersion();
|
||||
console.log( `\n${require('jsdoc/opts/args').help()}\n` );
|
||||
console.log('Visit https://jsdoc.app/ for more information.');
|
||||
|
||||
return Promise.resolve(0);
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
cli.runTests = () => {
|
||||
const path = require('jsdoc/path');
|
||||
|
||||
const runner = Promise.promisify(require( path.join(env.dirname, 'test/runner') ));
|
||||
|
||||
console.log('Running tests...');
|
||||
|
||||
return runner();
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
cli.getVersion = () => `JSDoc ${env.version.number} (${env.version.revision})`;
|
||||
|
||||
// TODO: docs
|
||||
cli.printVersion = () => {
|
||||
console.log( cli.getVersion() );
|
||||
|
||||
return Promise.resolve(0);
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
cli.main = () => {
|
||||
cli.scanFiles();
|
||||
|
||||
if (env.sourceFiles.length === 0) {
|
||||
console.log('There are no input files to process.');
|
||||
|
||||
return Promise.resolve(0);
|
||||
} else {
|
||||
return cli.createParser()
|
||||
.parseFiles()
|
||||
.processParseResults()
|
||||
.then(() => {
|
||||
env.run.finish = new Date();
|
||||
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function readPackageJson(filepath) {
|
||||
const 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() {
|
||||
const Readme = require('jsdoc/readme');
|
||||
|
||||
let packageJson;
|
||||
let readmeHtml;
|
||||
let sourceFile;
|
||||
let 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 (let 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 = () => {
|
||||
const Filter = require('jsdoc/src/filter').Filter;
|
||||
|
||||
let 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 ? env.conf.recurseDepth : undefined), filter);
|
||||
}
|
||||
|
||||
return cli;
|
||||
};
|
||||
|
||||
function resolvePluginPaths(paths) {
|
||||
const path = require('jsdoc/path');
|
||||
|
||||
const pluginPaths = [];
|
||||
|
||||
paths.forEach(plugin => {
|
||||
const basename = path.basename(plugin);
|
||||
const dirname = path.dirname(plugin);
|
||||
const pluginPath = path.getResourcePath(dirname, basename);
|
||||
|
||||
if (!pluginPath) {
|
||||
logger.error('Unable to find the plugin "%s"', plugin);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
pluginPaths.push( pluginPath );
|
||||
});
|
||||
|
||||
return pluginPaths;
|
||||
}
|
||||
|
||||
cli.createParser = () => {
|
||||
const handlers = require('jsdoc/src/handlers');
|
||||
const parser = require('jsdoc/src/parser');
|
||||
const 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 = () => {
|
||||
const augment = require('jsdoc/augment');
|
||||
const borrow = require('jsdoc/borrow');
|
||||
const Package = require('jsdoc/package').Package;
|
||||
|
||||
let docs;
|
||||
let 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('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 = () => {
|
||||
if (env.opts.explain) {
|
||||
cli.dumpParseResults();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
else {
|
||||
cli.resolveTutorials();
|
||||
|
||||
return cli.generateDocs();
|
||||
}
|
||||
};
|
||||
|
||||
cli.dumpParseResults = () => {
|
||||
console.log(require('jsdoc/util/dumper').dump(props.docs));
|
||||
|
||||
return cli;
|
||||
};
|
||||
|
||||
cli.resolveTutorials = () => {
|
||||
const resolver = require('jsdoc/tutorial/resolver');
|
||||
|
||||
if (env.opts.tutorials) {
|
||||
resolver.load(env.opts.tutorials);
|
||||
resolver.resolve();
|
||||
}
|
||||
|
||||
return cli;
|
||||
};
|
||||
|
||||
cli.generateDocs = () => {
|
||||
const path = require('jsdoc/path');
|
||||
const resolver = require('jsdoc/tutorial/resolver');
|
||||
const { taffy } = require('@jsdoc/salty');
|
||||
|
||||
let template;
|
||||
|
||||
env.opts.template = (() => {
|
||||
const publish = env.opts.template || 'templates/default';
|
||||
const 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') {
|
||||
let publishPromise;
|
||||
|
||||
logger.info('Generating output files...');
|
||||
publishPromise = template.publish(
|
||||
taffy(props.docs),
|
||||
env.opts,
|
||||
resolver.root
|
||||
);
|
||||
|
||||
return Promise.resolve(publishPromise);
|
||||
}
|
||||
else {
|
||||
logger.fatal(`${env.opts.template} does not export a "publish" function. Global "publish" functions are no longer supported.`);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
cli.exit = (exitCode, message) => {
|
||||
if (exitCode > 0 && message) {
|
||||
console.error(message);
|
||||
}
|
||||
process.on('exit', () => { process.exit(exitCode); });
|
||||
};
|
||||
|
||||
return cli;
|
||||
})();
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"tags": {
|
||||
"allowUnknownTags": true
|
||||
},
|
||||
"source": {
|
||||
"includePattern": ".+\\.js(doc|x)?$",
|
||||
"excludePattern": "(^|\\/|\\\\)_"
|
||||
},
|
||||
"plugins": [],
|
||||
"templates": {
|
||||
"cleverLinks": false,
|
||||
"monospaceLinks": false,
|
||||
"default": {
|
||||
"outputSourceFiles": true
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// initialize the environment for Node.js
|
||||
(() => {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let env;
|
||||
let jsdocPath = __dirname;
|
||||
const pwd = process.cwd();
|
||||
|
||||
// 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.
|
||||
/* eslint-disable no-global-assign, no-redeclare */
|
||||
require = require('requizzle')({
|
||||
requirePaths: {
|
||||
before: [path.join(__dirname, 'lib')],
|
||||
after: [path.join(__dirname, 'node_modules')]
|
||||
},
|
||||
infect: true
|
||||
});
|
||||
/* eslint-enable no-global-assign, no-redeclare */
|
||||
|
||||
// resolve the path if it's a symlink
|
||||
if ( fs.statSync(jsdocPath).isSymbolicLink() ) {
|
||||
jsdocPath = path.resolve( path.dirname(jsdocPath), fs.readlinkSync(jsdocPath) );
|
||||
}
|
||||
|
||||
env = require('./lib/jsdoc/env');
|
||||
env.dirname = jsdocPath;
|
||||
env.pwd = pwd;
|
||||
env.args = process.argv.slice(2);
|
||||
})();
|
||||
|
||||
/**
|
||||
* Data about the environment in which JSDoc is running, including the configuration settings that
|
||||
* were used to run JSDoc.
|
||||
*
|
||||
* @deprecated As of JSDoc 3.4.0. Use `require('jsdoc/env')` to access the `env` object. The global
|
||||
* `env` object will be removed in a future release.
|
||||
* @namespace
|
||||
* @name env
|
||||
*/
|
||||
global.env = (() => require('./lib/jsdoc/env'))();
|
||||
|
||||
/**
|
||||
* Data that must be shared across the entire application.
|
||||
*
|
||||
* @deprecated As of JSDoc 3.4.0. Avoid using the `app` object. The global `app` object and the
|
||||
* `jsdoc/app` module will be removed in a future release.
|
||||
* @namespace
|
||||
* @name app
|
||||
*/
|
||||
global.app = (() => require('./lib/jsdoc/app'))();
|
||||
|
||||
(() => {
|
||||
const env = global.env;
|
||||
const cli = require('./cli');
|
||||
|
||||
function cb(errorCode) {
|
||||
cli.logFinish();
|
||||
cli.exit(errorCode || 0);
|
||||
}
|
||||
|
||||
cli.setVersionInfo()
|
||||
.loadConfig();
|
||||
|
||||
if (!env.opts.test) {
|
||||
cli.configureLogger();
|
||||
}
|
||||
|
||||
cli.logStart();
|
||||
|
||||
if (env.opts.debug) {
|
||||
/**
|
||||
* Recursively print an object's properties to stdout. This method is safe to use with
|
||||
* objects that contain circular references.
|
||||
*
|
||||
* This method is available only when JSDoc is run with the `--debug` option.
|
||||
*
|
||||
* @global
|
||||
* @name dump
|
||||
* @private
|
||||
* @param {...*} obj - Object(s) to print to stdout.
|
||||
*/
|
||||
global.dump = (...args) => {
|
||||
console.log(require('./lib/jsdoc/util/dumper').dump(args));
|
||||
};
|
||||
}
|
||||
|
||||
cli.runCommand(cb);
|
||||
})();
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Objects that are shared across the entire application.
|
||||
*
|
||||
* @deprecated As of JSDoc 3.4.0. Do not use this module. It will be removed in a future release.
|
||||
* @module jsdoc/app
|
||||
*/
|
||||
module.exports = {
|
||||
/**
|
||||
* Namespace for shared objects.
|
||||
*
|
||||
* @namespace
|
||||
* @type {Object}
|
||||
*/
|
||||
jsdoc: {
|
||||
name: require('jsdoc/name'),
|
||||
parser: null,
|
||||
scanner: new (require('jsdoc/src/scanner').Scanner)()
|
||||
}
|
||||
};
|
||||
+608
@@ -0,0 +1,608 @@
|
||||
/**
|
||||
* Provides methods for augmenting the parse results based on their content.
|
||||
* @module jsdoc/augment
|
||||
*/
|
||||
|
||||
const doop = require('jsdoc/util/doop');
|
||||
const jsdoc = {
|
||||
doclet: require('jsdoc/doclet')
|
||||
};
|
||||
const name = require('jsdoc/name');
|
||||
|
||||
const hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
|
||||
function mapDependencies(index, propertyName) {
|
||||
const dependencies = {};
|
||||
let doc;
|
||||
let doclets;
|
||||
const kinds = ['class', 'external', 'interface', 'mixin'];
|
||||
let len = 0;
|
||||
|
||||
Object.keys(index).forEach(indexName => {
|
||||
doclets = index[indexName];
|
||||
for (let i = 0, ii = doclets.length; i < ii; i++) {
|
||||
doc = doclets[i];
|
||||
if (kinds.includes(doc.kind)) {
|
||||
dependencies[indexName] = {};
|
||||
if (hasOwnProp.call(doc, propertyName)) {
|
||||
len = doc[propertyName].length;
|
||||
for (let j = 0; j < len; j++) {
|
||||
dependencies[indexName][doc[propertyName][j]] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
class Sorter {
|
||||
constructor(dependencies) {
|
||||
this.dependencies = dependencies;
|
||||
this.visited = {};
|
||||
this.sorted = [];
|
||||
}
|
||||
|
||||
visit(key) {
|
||||
if (!(key in this.visited)) {
|
||||
this.visited[key] = true;
|
||||
|
||||
if (this.dependencies[key]) {
|
||||
Object.keys(this.dependencies[key]).forEach(path => {
|
||||
this.visit(path);
|
||||
});
|
||||
}
|
||||
|
||||
this.sorted.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
sort() {
|
||||
Object.keys(this.dependencies).forEach(key => {
|
||||
this.visit(key);
|
||||
});
|
||||
|
||||
return this.sorted;
|
||||
}
|
||||
}
|
||||
|
||||
function sort(dependencies) {
|
||||
const sorter = new Sorter(dependencies);
|
||||
|
||||
return sorter.sort();
|
||||
}
|
||||
|
||||
function getMembers(longname, {index}, scopes) {
|
||||
const memberof = index.memberof[longname] || [];
|
||||
const members = [];
|
||||
|
||||
memberof.forEach(candidate => {
|
||||
if (scopes.includes(candidate.scope)) {
|
||||
members.push(candidate);
|
||||
}
|
||||
});
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
function getDocumentedLongname(longname, {index}) {
|
||||
const doclets = index.documented[longname] || [];
|
||||
|
||||
return doclets[doclets.length - 1];
|
||||
}
|
||||
|
||||
function addDocletProperty(doclets, propName, value) {
|
||||
for (let i = 0, l = doclets.length; i < l; i++) {
|
||||
doclets[i][propName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function reparentDoclet({longname}, child) {
|
||||
const parts = name.shorten(child.longname);
|
||||
|
||||
parts.memberof = longname;
|
||||
child.memberof = longname;
|
||||
child.longname = name.combine(parts);
|
||||
}
|
||||
|
||||
function parentIsClass({kind}) {
|
||||
return kind === 'class';
|
||||
}
|
||||
|
||||
function staticToInstance(doclet) {
|
||||
const 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 {module:jsdoc/doclet.Doclet} doclet - The doclet to be added.
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the index of doclets whose `undocumented` property is not `true`.
|
||||
*
|
||||
* @private
|
||||
* @param {module:jsdoc/doclet.Doclet} doclet - The doclet to be added to the index.
|
||||
* @param {Object.<string, Array.<module:jsdoc/doclet.Doclet>>} documented - The index of doclets
|
||||
* whose `undocumented` property is not `true`.
|
||||
* @return {void}
|
||||
*/
|
||||
function updateDocumentedDoclets(doclet, documented) {
|
||||
if ( !hasOwnProp.call(documented, doclet.longname) ) {
|
||||
documented[doclet.longname] = [];
|
||||
}
|
||||
|
||||
documented[doclet.longname].push(doclet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the index of doclets with a `memberof` value.
|
||||
*
|
||||
* @private
|
||||
* @param {module:jsdoc/doclet.Doclet} doclet - The doclet to be added to the index.
|
||||
* @param {Object.<string, Array.<module:jsdoc/doclet.Doclet>>} memberof - The index of doclets
|
||||
* with a `memberof` value.
|
||||
* @return {void}
|
||||
*/
|
||||
function updateMemberofDoclets(doclet, memberof) {
|
||||
if (doclet.memberof) {
|
||||
if ( !hasOwnProp.call(memberof, doclet.memberof) ) {
|
||||
memberof[doclet.memberof] = [];
|
||||
}
|
||||
|
||||
memberof[doclet.memberof].push(doclet);
|
||||
}
|
||||
}
|
||||
|
||||
function explicitlyInherits(doclets) {
|
||||
let doclet;
|
||||
let inherits = false;
|
||||
|
||||
for (let 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 changeMemberof(longname, newMemberof) {
|
||||
const atoms = name.shorten(longname);
|
||||
|
||||
atoms.memberof = newMemberof;
|
||||
|
||||
return name.combine(atoms);
|
||||
}
|
||||
|
||||
// TODO: try to reduce overlap with similar methods
|
||||
function getInheritedAdditions(doclets, docs, {documented, memberof}) {
|
||||
let additionIndexes;
|
||||
const additions = [];
|
||||
let childDoclet;
|
||||
let childLongname;
|
||||
let doc;
|
||||
let parentDoclet;
|
||||
let parentMembers;
|
||||
let parents;
|
||||
let member;
|
||||
let parts;
|
||||
|
||||
// doclets will be undefined if the inherited symbol isn't documented
|
||||
doclets = doclets || [];
|
||||
|
||||
for (let i = 0, ii = doclets.length; i < ii; i++) {
|
||||
doc = doclets[i];
|
||||
parents = doc.augments;
|
||||
|
||||
if ( parents && (doc.kind === 'class' || doc.kind === 'interface') ) {
|
||||
// reset the lookup table of added doclet indexes by longname
|
||||
additionIndexes = {};
|
||||
|
||||
for (let j = 0, jj = parents.length; j < jj; j++) {
|
||||
parentMembers = getMembers(parents[j], docs, ['instance']);
|
||||
|
||||
for (let k = 0, kk = parentMembers.length; k < kk; k++) {
|
||||
parentDoclet = parentMembers[k];
|
||||
|
||||
// We only care about symbols that are documented.
|
||||
if (parentDoclet.undocumented) {
|
||||
continue;
|
||||
}
|
||||
|
||||
childLongname = changeMemberof(parentDoclet.longname, doc.longname);
|
||||
childDoclet = getDocumentedLongname(childLongname, docs) || {};
|
||||
|
||||
// We don't want to fold in properties from the child doclet if it had an
|
||||
// `@inheritdoc` tag.
|
||||
if (hasOwnProp.call(childDoclet, 'inheritdoc')) {
|
||||
childDoclet = {};
|
||||
}
|
||||
|
||||
member = jsdoc.doclet.combine(childDoclet, parentDoclet);
|
||||
|
||||
if (!member.inherited) {
|
||||
member.inherits = member.longname;
|
||||
}
|
||||
member.inherited = true;
|
||||
|
||||
member.memberof = doc.longname;
|
||||
parts = name.shorten(member.longname);
|
||||
parts.memberof = doc.longname;
|
||||
member.longname = name.combine(parts);
|
||||
|
||||
// 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.)
|
||||
if ( hasOwnProp.call(docs.index.longname, member.longname) ) {
|
||||
member.overrides = parentDoclet.longname;
|
||||
}
|
||||
else {
|
||||
delete member.overrides;
|
||||
}
|
||||
|
||||
// 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);
|
||||
updateDocumentedDoclets(member, documented);
|
||||
updateMemberofDoclets(member, memberof);
|
||||
}
|
||||
// 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);
|
||||
updateDocumentedDoclets(member, documented);
|
||||
updateMemberofDoclets(member, memberof);
|
||||
|
||||
// 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',
|
||||
parentDoclet.longname);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return additions;
|
||||
}
|
||||
|
||||
function updateMixes(mixedDoclet, mixedLongname) {
|
||||
let idx;
|
||||
let mixedName;
|
||||
let 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
|
||||
// TODO: why do we run a map if we always shorten the same value? this looks like a bug...
|
||||
names = mixedDoclet.mixes.map(() => 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 similar methods
|
||||
function getMixedInAdditions(mixinDoclets, allDoclets, {documented, memberof}) {
|
||||
let additionIndexes;
|
||||
const additions = [];
|
||||
const commentedDoclets = documented;
|
||||
let doclet;
|
||||
let mixedDoclet;
|
||||
let mixedDoclets;
|
||||
let mixes;
|
||||
|
||||
// mixinDoclets will be undefined if the mixed-in symbol isn't documented
|
||||
mixinDoclets = mixinDoclets || [];
|
||||
|
||||
for (let 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 (let j = 0, jj = mixes.length; j < jj; j++) {
|
||||
mixedDoclets = getMembers(mixes[j], allDoclets, ['static']);
|
||||
|
||||
for (let k = 0, kk = mixedDoclets.length; k < kk; k++) {
|
||||
// We only care about symbols that are documented.
|
||||
if (mixedDoclets[k].undocumented) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
updateDocumentedDoclets(mixedDoclet, commentedDoclets);
|
||||
updateMemberofDoclets(mixedDoclet, memberof);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return additions;
|
||||
}
|
||||
|
||||
function updateImplements(implDoclets, implementedLongname) {
|
||||
if ( !Array.isArray(implDoclets) ) {
|
||||
implDoclets = [implDoclets];
|
||||
}
|
||||
|
||||
implDoclets.forEach(implDoclet => {
|
||||
if ( !hasOwnProp.call(implDoclet, 'implements') ) {
|
||||
implDoclet.implements = [];
|
||||
}
|
||||
|
||||
if (!implDoclet.implements.includes(implementedLongname)) {
|
||||
implDoclet.implements.push(implementedLongname);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: try to reduce overlap with similar methods
|
||||
function getImplementedAdditions(implDoclets, allDoclets, {documented, memberof}) {
|
||||
let additionIndexes;
|
||||
const additions = [];
|
||||
let childDoclet;
|
||||
let childLongname;
|
||||
const commentedDoclets = documented;
|
||||
let doclet;
|
||||
let implementations;
|
||||
let implExists;
|
||||
let implementationDoclet;
|
||||
let interfaceDoclets;
|
||||
let parentDoclet;
|
||||
|
||||
// interfaceDoclets will be undefined if the implemented symbol isn't documented
|
||||
implDoclets = implDoclets || [];
|
||||
|
||||
for (let i = 0, ii = implDoclets.length; i < ii; i++) {
|
||||
doclet = implDoclets[i];
|
||||
implementations = doclet.implements;
|
||||
|
||||
if (implementations) {
|
||||
// reset the lookup table of added doclet indexes by longname
|
||||
additionIndexes = {};
|
||||
|
||||
for (let j = 0, jj = implementations.length; j < jj; j++) {
|
||||
interfaceDoclets = getMembers(implementations[j], allDoclets, ['instance']);
|
||||
|
||||
for (let k = 0, kk = interfaceDoclets.length; k < kk; k++) {
|
||||
parentDoclet = interfaceDoclets[k];
|
||||
|
||||
// We only care about symbols that are documented.
|
||||
if (parentDoclet.undocumented) {
|
||||
continue;
|
||||
}
|
||||
|
||||
childLongname = changeMemberof(parentDoclet.longname, doclet.longname);
|
||||
childDoclet = getDocumentedLongname(childLongname, allDoclets) || {};
|
||||
|
||||
// We don't want to fold in properties from the child doclet if it had an
|
||||
// `@inheritdoc` tag.
|
||||
if (hasOwnProp.call(childDoclet, 'inheritdoc')) {
|
||||
childDoclet = {};
|
||||
}
|
||||
|
||||
implementationDoclet = jsdoc.doclet.combine(childDoclet, parentDoclet);
|
||||
|
||||
reparentDoclet(doclet, implementationDoclet);
|
||||
updateImplements(implementationDoclet, parentDoclet.longname);
|
||||
|
||||
// If there's no implementation, move along.
|
||||
implExists = hasOwnProp.call(allDoclets.index.longname,
|
||||
implementationDoclet.longname);
|
||||
if (!implExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the interface's docs unless the implementation is already documented.
|
||||
if ( !hasOwnProp.call(commentedDoclets, implementationDoclet.longname) ) {
|
||||
updateAddedDoclets(implementationDoclet, additions, additionIndexes);
|
||||
updateDocumentedDoclets(implementationDoclet, commentedDoclets);
|
||||
updateMemberofDoclets(implementationDoclet, memberof);
|
||||
}
|
||||
// If the implementation used an @inheritdoc or @override tag, add the
|
||||
// interface's docs, and ignore the existing doclets.
|
||||
else if ( explicitlyInherits(commentedDoclets[implementationDoclet.longname]) ) {
|
||||
// Ignore any existing doclets. (This is safe because we only get here if
|
||||
// `implementationDoclet.longname` is an own property of
|
||||
// `commentedDoclets`.)
|
||||
addDocletProperty(commentedDoclets[implementationDoclet.longname], 'ignore',
|
||||
true);
|
||||
|
||||
updateAddedDoclets(implementationDoclet, additions, additionIndexes);
|
||||
updateDocumentedDoclets(implementationDoclet, commentedDoclets);
|
||||
updateMemberofDoclets(implementationDoclet, memberof);
|
||||
|
||||
// Remove property that's no longer accurate.
|
||||
if (implementationDoclet.virtual) {
|
||||
delete implementationDoclet.virtual;
|
||||
}
|
||||
// Remove properties that we no longer need.
|
||||
if (implementationDoclet.inheritdoc) {
|
||||
delete implementationDoclet.inheritdoc;
|
||||
}
|
||||
if (implementationDoclet.override) {
|
||||
delete implementationDoclet.override;
|
||||
}
|
||||
}
|
||||
// If there's an implementation, and it's documented, update the doclets to
|
||||
// indicate what the implementation is implementing.
|
||||
else {
|
||||
updateImplements(commentedDoclets[implementationDoclet.longname],
|
||||
parentDoclet.longname);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return additions;
|
||||
}
|
||||
|
||||
function augment(doclets, propertyName, docletFinder) {
|
||||
const index = doclets.index.longname;
|
||||
const dependencies = sort( mapDependencies(index, propertyName) );
|
||||
|
||||
dependencies.forEach(depName => {
|
||||
const additions = docletFinder(index[depName], doclets, doclets.index);
|
||||
|
||||
additions.forEach(addition => {
|
||||
const 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.
|
||||
* @return {void}
|
||||
*/
|
||||
exports.addInherited = 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.
|
||||
* @return {void}
|
||||
*/
|
||||
exports.addMixedIn = doclets => {
|
||||
augment(doclets, 'mixes', getMixedInAdditions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add and 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`
|
||||
*
|
||||
* If `ClassA#myMethod` used the `@override` or `@inheritdoc` tag, calling this method would also
|
||||
* generate a new doclet that reflects the interface's documentation for `InterfaceA#myMethod`.
|
||||
*
|
||||
* @param {!Array.<module:jsdoc/doclet.Doclet>} docs - The doclets generated by JSDoc.
|
||||
* @param {!Object} doclets.index - The doclet index.
|
||||
* @return {void}
|
||||
*/
|
||||
exports.addImplemented = doclets => {
|
||||
augment(doclets, 'implements', getImplementedAdditions);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 = doclets => {
|
||||
exports.addMixedIn(doclets);
|
||||
exports.addImplemented(doclets);
|
||||
exports.addInherited(doclets);
|
||||
// look for implemented doclets again, in case we inherited an interface
|
||||
exports.addImplemented(doclets);
|
||||
};
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* A collection of functions relating to resolving @borrows tags in JSDoc symbols.
|
||||
* @module jsdoc/borrow
|
||||
*/
|
||||
const doop = require('jsdoc/util/doop');
|
||||
const SCOPE = require('jsdoc/name').SCOPE;
|
||||
|
||||
function cloneBorrowedDoclets({borrowed, longname}, doclets) {
|
||||
borrowed.forEach(({from, as}) => {
|
||||
const borrowedDoclets = doclets.index.longname[from];
|
||||
let borrowedAs = as || from;
|
||||
let parts;
|
||||
let scopePunc;
|
||||
|
||||
if (borrowedDoclets) {
|
||||
borrowedAs = borrowedAs.replace(/^prototype\./, SCOPE.PUNC.INSTANCE);
|
||||
doop(borrowedDoclets).forEach(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 = longname;
|
||||
clone.longname = clone.memberof + scopePunc + clone.name;
|
||||
doclets.push(clone);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
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 = doclets => {
|
||||
for (let doclet of doclets.index.borrowed) {
|
||||
cloneBorrowedDoclets(doclet, doclets);
|
||||
delete doclet.borrowed;
|
||||
}
|
||||
|
||||
doclets.index.borrowed = [];
|
||||
};
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @module jsdoc/config
|
||||
*/
|
||||
const stripBom = require('jsdoc/util/stripbom');
|
||||
const stripJsonComments = require('strip-json-comments');
|
||||
|
||||
function mergeRecurse(target, source) {
|
||||
Object.keys(source).forEach(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
|
||||
const defaults = {
|
||||
plugins: [],
|
||||
recurseDepth: 10,
|
||||
source: {
|
||||
includePattern: '.+\\.js(doc|x)?$',
|
||||
excludePattern: ''
|
||||
},
|
||||
sourceType: 'module',
|
||||
tags: {
|
||||
allowUnknownTags: true,
|
||||
dictionaries: ['jsdoc', 'closure']
|
||||
},
|
||||
templates: {
|
||||
monospaceLinks: false,
|
||||
cleverLinks: false
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a JSDoc application configuration.
|
||||
*/
|
||||
class Config {
|
||||
/**
|
||||
* @param {(string|object)} [jsonOrObject] - The contents of config.json, or a JavaScript object
|
||||
* exported from a .js config file.
|
||||
*/
|
||||
constructor(jsonOrObject) {
|
||||
if (typeof jsonOrObject === 'undefined') {
|
||||
jsonOrObject = {};
|
||||
}
|
||||
|
||||
if (typeof jsonOrObject === 'string') {
|
||||
jsonOrObject = JSON.parse( (stripJsonComments(stripBom.strip(jsonOrObject)) || '{}') );
|
||||
}
|
||||
|
||||
if (typeof jsonOrObject !== 'object') {
|
||||
jsonOrObject = {};
|
||||
}
|
||||
|
||||
this._config = mergeRecurse(defaults, jsonOrObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the merged configuration values.
|
||||
*/
|
||||
get() {
|
||||
return this._config;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Config;
|
||||
+562
@@ -0,0 +1,562 @@
|
||||
/**
|
||||
* @module jsdoc/doclet
|
||||
*/
|
||||
const _ = require('underscore');
|
||||
const jsdoc = {
|
||||
env: require('jsdoc/env'),
|
||||
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')
|
||||
},
|
||||
util: {
|
||||
doop: require('jsdoc/util/doop')
|
||||
}
|
||||
};
|
||||
const path = require('jsdoc/path');
|
||||
const Syntax = jsdoc.src.Syntax;
|
||||
const util = require('util');
|
||||
|
||||
function applyTag(doclet, {title, value}) {
|
||||
if (title === 'name') {
|
||||
doclet.name = value;
|
||||
}
|
||||
|
||||
if (title === 'kind') {
|
||||
doclet.kind = value;
|
||||
}
|
||||
|
||||
if (title === 'description') {
|
||||
doclet.description = value;
|
||||
}
|
||||
}
|
||||
|
||||
function fakeMeta(node) {
|
||||
return {
|
||||
type: node ? node.type : null,
|
||||
node: node
|
||||
};
|
||||
}
|
||||
|
||||
// use the meta info about the source code to guess what the doclet kind should be
|
||||
// TODO: set this elsewhere (maybe jsdoc/src/astnode.getInfo)
|
||||
function codeToKind(code) {
|
||||
const isFunction = jsdoc.src.astnode.isFunction;
|
||||
let kind = 'member';
|
||||
const node = code.node;
|
||||
|
||||
if ( isFunction(code.type) && code.type !== Syntax.MethodDefinition ) {
|
||||
kind = 'function';
|
||||
}
|
||||
else if (code.type === Syntax.MethodDefinition) {
|
||||
if (code.node.kind === 'constructor') {
|
||||
kind = 'class';
|
||||
}
|
||||
else if (code.node.kind !== 'get' && code.node.kind !== 'set') {
|
||||
kind = 'function';
|
||||
}
|
||||
}
|
||||
else if (code.type === Syntax.ClassDeclaration || code.type === Syntax.ClassExpression) {
|
||||
kind = 'class';
|
||||
}
|
||||
else if (code.type === Syntax.ExportAllDeclaration) {
|
||||
// this value will often be an Identifier for a variable, which isn't very useful
|
||||
kind = codeToKind(fakeMeta(node.source));
|
||||
}
|
||||
else if (code.type === Syntax.ExportDefaultDeclaration ||
|
||||
code.type === Syntax.ExportNamedDeclaration) {
|
||||
kind = codeToKind(fakeMeta(node.declaration));
|
||||
}
|
||||
else if (code.type === Syntax.ExportSpecifier) {
|
||||
// this value will often be an Identifier for a variable, which isn't very useful
|
||||
kind = codeToKind(fakeMeta(node.local));
|
||||
}
|
||||
else if ( code.node && code.node.parent && isFunction(code.node.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 =
|
||||
// remove opening slash+stars
|
||||
docletSrc.replace(/^\/\*\*+/, '')
|
||||
// replace closing star slash with end-marker
|
||||
.replace(/\**\*\/$/, '\\Z')
|
||||
// remove left margin like: spaces+star or spaces+end-marker
|
||||
.replace(/^\s*(\* ?|\\Z)/gm, '')
|
||||
// remove end-marker
|
||||
.replace(/\s*\\Z$/g, '');
|
||||
|
||||
return docletSrc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the raw source of the doclet comment into an array of pseudo-Tag objects.
|
||||
* @private
|
||||
*/
|
||||
function toTags(docletSrc) {
|
||||
let parsedTag;
|
||||
const tagData = [];
|
||||
let tagText;
|
||||
let 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($ => {
|
||||
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, {code}) {
|
||||
let isClass;
|
||||
|
||||
if (!/^\s*@/.test(docletSrc) && docletSrc.replace(/\s/g, '').length) {
|
||||
isClass = code &&
|
||||
(code.type === Syntax.ClassDeclaration ||
|
||||
code.type === Syntax.ClassExpression);
|
||||
|
||||
docletSrc = `${isClass ? '@classdesc' : '@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);
|
||||
};
|
||||
|
||||
function removeGlobal(longname) {
|
||||
const globalRegexp = new RegExp(`^${jsdoc.name.LONGNAMES.GLOBAL}\\.?`);
|
||||
|
||||
return longname.replace(globalRegexp, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
function dooper(source, target, properties) {
|
||||
properties.forEach(property => {
|
||||
switch (typeof source[property]) {
|
||||
case 'function':
|
||||
// do nothing
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
target[property] = jsdoc.util.doop(source[property]);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
target[property] = source[property];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy all but a list of excluded properties from one of two doclets onto a target doclet. Prefers
|
||||
* the primary doclet over the secondary doclet.
|
||||
*
|
||||
* @private
|
||||
* @param {module:jsdoc/doclet.Doclet} primary - The primary doclet.
|
||||
* @param {module:jsdoc/doclet.Doclet} secondary - The secondary doclet.
|
||||
* @param {module:jsdoc/doclet.Doclet} target - The doclet to which properties will be copied.
|
||||
* @param {Array.<string>} exclude - The names of properties to exclude from copying.
|
||||
*/
|
||||
function copyMostProperties(primary, secondary, target, exclude) {
|
||||
const primaryProperties = _.difference(Object.getOwnPropertyNames(primary), exclude);
|
||||
const secondaryProperties = _.difference(Object.getOwnPropertyNames(secondary),
|
||||
exclude.concat(primaryProperties));
|
||||
|
||||
dooper(primary, target, primaryProperties);
|
||||
dooper(secondary, target, secondaryProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy specific properties from one of two doclets onto a target doclet, as long as the property
|
||||
* has a non-falsy value and a length greater than 0. Prefers the primary doclet over the secondary
|
||||
* doclet.
|
||||
*
|
||||
* @private
|
||||
* @param {module:jsdoc/doclet.Doclet} primary - The primary doclet.
|
||||
* @param {module:jsdoc/doclet.Doclet} secondary - The secondary doclet.
|
||||
* @param {module:jsdoc/doclet.Doclet} target - The doclet to which properties will be copied.
|
||||
* @param {Array.<string>} include - The names of properties to copy.
|
||||
*/
|
||||
function copySpecificProperties(primary, secondary, target, include) {
|
||||
include.forEach(property => {
|
||||
if ({}.hasOwnProperty.call(primary, property) && primary[property] &&
|
||||
primary[property].length) {
|
||||
target[property] = jsdoc.util.doop(primary[property]);
|
||||
}
|
||||
else if ({}.hasOwnProperty.call(secondary, property) && secondary[property] &&
|
||||
secondary[property].length) {
|
||||
target[property] = jsdoc.util.doop(secondary[property]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single JSDoc comment.
|
||||
*
|
||||
* @alias module:jsdoc/doclet.Doclet
|
||||
*/
|
||||
class Doclet {
|
||||
/**
|
||||
* Create a doclet.
|
||||
*
|
||||
* @param {string} docletSrc - The raw source code of the jsdoc comment.
|
||||
* @param {object=} meta - Properties describing the code related to this comment.
|
||||
*/
|
||||
constructor(docletSrc, meta = {}) {
|
||||
let newTags = [];
|
||||
|
||||
/** The original text of the comment from the source code. */
|
||||
this.comment = docletSrc;
|
||||
this.setMeta(meta);
|
||||
|
||||
docletSrc = unwrap(docletSrc);
|
||||
docletSrc = fixDescription(docletSrc, meta);
|
||||
|
||||
newTags = toTags.call(this, docletSrc);
|
||||
|
||||
for (let 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. */
|
||||
postProcess() {
|
||||
let i;
|
||||
let 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.
|
||||
*/
|
||||
addTag(title, text) {
|
||||
const tagDef = jsdoc.tag.dictionary.lookUp(title);
|
||||
const 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the doclet's `memberof` property.
|
||||
*
|
||||
* @param {string} sid - The longname of the doclet's parent symbol.
|
||||
*/
|
||||
setMemberof(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.
|
||||
*/
|
||||
setLongname(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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
setScope(scope) {
|
||||
let errorMessage;
|
||||
let filepath;
|
||||
const scopeNames = _.values(jsdoc.name.SCOPE.NAMES);
|
||||
|
||||
if (!scopeNames.includes(scope)) {
|
||||
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.
|
||||
*/
|
||||
borrow(source, target) {
|
||||
const 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);
|
||||
}
|
||||
|
||||
mix(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.
|
||||
*/
|
||||
augment(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
|
||||
*/
|
||||
setMeta(meta) {
|
||||
let pathname;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* The column number of the code associated with this doclet.
|
||||
* @type number
|
||||
*/
|
||||
this.meta.columnno = meta.columnno;
|
||||
|
||||
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.
|
||||
* @type {string}
|
||||
*/
|
||||
this.meta.code.name = meta.code.name;
|
||||
}
|
||||
if (meta.code.type) {
|
||||
/**
|
||||
* The type of the symbol in the source code.
|
||||
* @type {string}
|
||||
*/
|
||||
this.meta.code.type = meta.code.type;
|
||||
}
|
||||
if (meta.code.node) {
|
||||
Object.defineProperty(this.meta.code, 'node', {
|
||||
value: meta.code.node,
|
||||
enumerable: false
|
||||
});
|
||||
}
|
||||
if (meta.code.funcscope) {
|
||||
this.meta.code.funcscope = meta.code.funcscope;
|
||||
}
|
||||
if (typeof meta.code.value !== 'undefined') {
|
||||
/**
|
||||
* The value of the symbol in the source code.
|
||||
* @type {*}
|
||||
*/
|
||||
this.meta.code.value = meta.code.value;
|
||||
}
|
||||
if (meta.code.paramnames) {
|
||||
this.meta.code.paramnames = meta.code.paramnames.slice(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.Doclet = Doclet;
|
||||
|
||||
/**
|
||||
* Combine two doclets into a new doclet.
|
||||
*
|
||||
* @param {module:jsdoc/doclet.Doclet} primary - The doclet whose properties will be used.
|
||||
* @param {module:jsdoc/doclet.Doclet} secondary - The doclet to use as a fallback for properties
|
||||
* that the primary doclet does not have.
|
||||
* @returns {module:jsdoc/doclet.Doclet} A new doclet that combines the primary and secondary
|
||||
* doclets.
|
||||
*/
|
||||
exports.combine = (primary, secondary) => {
|
||||
const copyMostPropertiesExclude = [
|
||||
'params',
|
||||
'properties',
|
||||
'undocumented'
|
||||
];
|
||||
const copySpecificPropertiesInclude = [
|
||||
'params',
|
||||
'properties'
|
||||
];
|
||||
const target = new Doclet('');
|
||||
|
||||
// First, copy most properties to the target doclet.
|
||||
copyMostProperties(primary, secondary, target, copyMostPropertiesExclude);
|
||||
// Then copy a few specific properties to the target doclet, as long as they're not falsy and
|
||||
// have a length greater than 0.
|
||||
copySpecificProperties(primary, secondary, target, copySpecificPropertiesInclude);
|
||||
|
||||
return target;
|
||||
};
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Data about the environment in which JSDoc is running, including the configuration settings that
|
||||
* were used to run JSDoc.
|
||||
*
|
||||
* @module jsdoc/env
|
||||
*/
|
||||
module.exports = {
|
||||
/**
|
||||
* The times at which JSDoc started and finished.
|
||||
*
|
||||
* @type {Object}
|
||||
* @property {Date} start - The time at which JSDoc started running.
|
||||
* @property {Date} finish - The time at which JSDoc finished running.
|
||||
*/
|
||||
run: {
|
||||
start: new Date(),
|
||||
finish: null
|
||||
},
|
||||
|
||||
/**
|
||||
* The command-line arguments passed to JSDoc.
|
||||
*
|
||||
* @type {Array<*>}
|
||||
*/
|
||||
args: [],
|
||||
|
||||
/**
|
||||
* The data parsed from JSDoc's configuration file.
|
||||
*
|
||||
* @type Object<string, *>
|
||||
*/
|
||||
conf: {},
|
||||
|
||||
/**
|
||||
* The absolute path to the base directory in which JSDoc is located. Set at startup.
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
dirname: null,
|
||||
|
||||
/**
|
||||
* The user's working directory at the time when JSDoc started running.
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
pwd: null,
|
||||
|
||||
/**
|
||||
* The command-line arguments, parsed into a key/value hash.
|
||||
*
|
||||
* @type {Object}
|
||||
* @example if (global.env.opts.help) { console.log('Helpful message.'); }
|
||||
*/
|
||||
opts: {},
|
||||
|
||||
/**
|
||||
* The source files that JSDoc will parse.
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof env
|
||||
*/
|
||||
sourceFiles: [],
|
||||
|
||||
/**
|
||||
* The JSDoc version number and revision date.
|
||||
*
|
||||
* @type {Object<string, string>}
|
||||
* @property {string} number - The JSDoc version number.
|
||||
* @property {string} revision - The JSDoc revision number, expressed as a UTC date string.
|
||||
*/
|
||||
version: {
|
||||
number: null,
|
||||
revision: null
|
||||
}
|
||||
};
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Extended version of the standard `fs` module.
|
||||
* @module jsdoc/fs
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const mkdirp = require('mkdirp');
|
||||
|
||||
const ls = exports.ls = (dir, recurse, _allFiles, _path) => {
|
||||
let file;
|
||||
let files;
|
||||
let 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 (let 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;
|
||||
};
|
||||
|
||||
exports.toDir = _path => {
|
||||
let 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 = _path => {
|
||||
if ( Array.isArray(_path) ) {
|
||||
_path = _path.join('');
|
||||
}
|
||||
|
||||
mkdirp.sync(_path);
|
||||
};
|
||||
|
||||
exports.copyFileSync = (inFile, outDir = '', fileName) => {
|
||||
fileName = fileName || path.basename(inFile);
|
||||
|
||||
fs.copyFileSync(inFile, path.join(outDir, fileName));
|
||||
};
|
||||
|
||||
const alwaysOverride = {
|
||||
'copyFileSync': true
|
||||
};
|
||||
|
||||
Object.keys(fs).forEach(member => {
|
||||
if (!alwaysOverride[member]) {
|
||||
exports[member] = fs[member];
|
||||
}
|
||||
});
|
||||
|
||||
+595
@@ -0,0 +1,595 @@
|
||||
/**
|
||||
* A collection of functions relating to JSDoc symbol name manipulation.
|
||||
* @module jsdoc/name
|
||||
*/
|
||||
const _ = require('underscore');
|
||||
const escape = require('escape-string-regexp');
|
||||
|
||||
const hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* Longnames that have a special meaning in JSDoc.
|
||||
*
|
||||
* @enum {string}
|
||||
* @static
|
||||
* @memberof module:jsdoc/name
|
||||
*/
|
||||
const 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.
|
||||
const MODULE_NAMESPACE = 'module:';
|
||||
|
||||
/**
|
||||
* Names and punctuation marks that identify doclet scopes.
|
||||
*
|
||||
* @enum {string}
|
||||
* @static
|
||||
* @memberof module:jsdoc/name
|
||||
*/
|
||||
const 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
|
||||
const scopeToPunc = exports.scopeToPunc = {
|
||||
'inner': SCOPE.PUNC.INNER,
|
||||
'instance': SCOPE.PUNC.INSTANCE,
|
||||
'static': SCOPE.PUNC.STATIC
|
||||
};
|
||||
const puncToScope = exports.puncToScope = _.invert(scopeToPunc);
|
||||
|
||||
const DEFAULT_SCOPE = SCOPE.NAMES.STATIC;
|
||||
const SCOPE_PUNC = _.values(SCOPE.PUNC);
|
||||
const SCOPE_PUNC_STRING = `[${SCOPE_PUNC.join()}]`;
|
||||
const REGEXP_LEADING_SCOPE = new RegExp(`^(${SCOPE_PUNC_STRING})`);
|
||||
const REGEXP_TRAILING_SCOPE = new RegExp(`(${SCOPE_PUNC_STRING})$`);
|
||||
|
||||
const DESCRIPTION = '(?:(?:[ \\t]*\\-\\s*|\\s+)(\\S[\\s\\S]*))?$';
|
||||
const REGEXP_DESCRIPTION = new RegExp(DESCRIPTION);
|
||||
const REGEXP_NAME_DESCRIPTION = new RegExp(`^(\\[[^\\]]+\\]|\\S+)${DESCRIPTION}`);
|
||||
|
||||
function nameIsLongname(name, memberof) {
|
||||
const regexp = new RegExp(`^${escape(memberof)}${SCOPE_PUNC_STRING}`);
|
||||
|
||||
return regexp.test(name);
|
||||
}
|
||||
|
||||
function prototypeToPunc(name) {
|
||||
// don't mangle symbols named "prototype"
|
||||
if (name === 'prototype') {
|
||||
return name;
|
||||
}
|
||||
|
||||
return name.replace(/(?:^|\.)prototype\.?/g, SCOPE.PUNC.INSTANCE);
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
/**
|
||||
* @param {string} name - The symbol's longname.
|
||||
* @return {string} The symbol's basename.
|
||||
*/
|
||||
exports.getBasename = name => {
|
||||
if (name !== undefined) {
|
||||
return name.replace(/^([$a-z_][$a-z_0-9]*).*?$/i, '$1');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// 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 = doclet => {
|
||||
let about = {};
|
||||
let memberof = doclet.memberof || '';
|
||||
let metaName;
|
||||
let name = doclet.name ? String(doclet.name) : '';
|
||||
let puncAndName;
|
||||
let puncAndNameIndex;
|
||||
|
||||
// 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;
|
||||
|
||||
// does the doclet have an alias that identifies the memberof? if so, use it
|
||||
if (doclet.alias) {
|
||||
about = exports.shorten(name);
|
||||
|
||||
if (about.memberof) {
|
||||
memberof = about.memberof;
|
||||
}
|
||||
}
|
||||
// member of a var in an outer scope?
|
||||
else 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 if (doclet.meta.code && doclet.meta.code.name) {
|
||||
// HACK: Handle cases where an ES 2015 class is a static memberof something else, and
|
||||
// the class has instance members. In these cases, we have to detect the instance
|
||||
// members' scope by looking at the meta info. There's almost certainly a better way to
|
||||
// do this...
|
||||
metaName = String(doclet.meta.code.name);
|
||||
puncAndName = SCOPE.PUNC.INSTANCE + doclet.name;
|
||||
puncAndNameIndex = metaName.indexOf(puncAndName);
|
||||
if ( puncAndNameIndex !== -1 &&
|
||||
(puncAndNameIndex === metaName.length - puncAndName.length) ) {
|
||||
doclet.scope = SCOPE.NAMES.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
doclet.scope = 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 = '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @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 = (longname, ns) => {
|
||||
const nameParts = exports.shorten(longname);
|
||||
const 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
|
||||
exports.stripNamespace = longname => longname.replace(/^[a-zA-Z]+:/, '');
|
||||
|
||||
/**
|
||||
* Check whether a parent longname is an ancestor of a child longname.
|
||||
*
|
||||
* @param {string} parent - The parent longname.
|
||||
* @param {string} child - The child longname.
|
||||
* @return {boolean} `true` if the parent is an ancestor of the child; otherwise, `false`.
|
||||
*/
|
||||
exports.hasAncestor = (parent, child) => {
|
||||
let hasAncestor = false;
|
||||
let memberof = child;
|
||||
|
||||
if (!parent || !child) {
|
||||
return hasAncestor;
|
||||
}
|
||||
|
||||
// fast path for obvious non-ancestors
|
||||
if (child.indexOf(parent) !== 0) {
|
||||
return hasAncestor;
|
||||
}
|
||||
|
||||
do {
|
||||
memberof = exports.shorten(memberof).memberof;
|
||||
|
||||
if (memberof === parent) {
|
||||
hasAncestor = true;
|
||||
}
|
||||
} while (!hasAncestor && memberof);
|
||||
|
||||
return hasAncestor;
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
function atomize(longname, sliceChars, forcedMemberof) {
|
||||
let i;
|
||||
let memberof = '';
|
||||
let name = '';
|
||||
let parts;
|
||||
let partsRegExp;
|
||||
let scopePunc = '';
|
||||
let token;
|
||||
const tokens = [];
|
||||
let variation;
|
||||
|
||||
// quoted strings in a longname are atomic, so we convert them to tokens:
|
||||
// foo["bar"] => foo.@{1}@
|
||||
// Foo.prototype["bar"] => Foo#@{1}
|
||||
longname = longname.replace(/(prototype|#)?(\[?["'].+?["']\]?)/g, ($, p1, p2) => {
|
||||
let punc = '';
|
||||
|
||||
// is there a leading bracket?
|
||||
if ( /^\[/.test(p2) ) {
|
||||
// is it a static or instance member?
|
||||
punc = p1 ? SCOPE.PUNC.INSTANCE : SCOPE.PUNC.STATIC;
|
||||
p2 = p2.replace(/^\[/g, '')
|
||||
.replace(/\]$/g, '');
|
||||
}
|
||||
|
||||
token = `@{${tokens.length}}@`;
|
||||
tokens.push(p2);
|
||||
|
||||
return punc + token;
|
||||
});
|
||||
|
||||
longname = prototypeToPunc(longname);
|
||||
|
||||
if (typeof 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 = (longname, forcedMemberof) => atomize(longname, SCOPE_PUNC, forcedMemberof);
|
||||
|
||||
// TODO: docs
|
||||
exports.combine = ({memberof, scope, name, variation}) => [
|
||||
(memberof || ''),
|
||||
(scope || ''),
|
||||
(name || ''),
|
||||
(variation || '')
|
||||
].join('');
|
||||
|
||||
// TODO: docs
|
||||
exports.stripVariation = name => {
|
||||
const parts = exports.shorten(name);
|
||||
|
||||
parts.variation = '';
|
||||
|
||||
return exports.combine(parts);
|
||||
};
|
||||
|
||||
function splitLongname(longname, options) {
|
||||
const chunks = [];
|
||||
let currentNameInfo;
|
||||
const nameInfo = {};
|
||||
let previousName = longname;
|
||||
const splitters = SCOPE_PUNC.concat('/');
|
||||
|
||||
options = _.defaults(options || {}, {
|
||||
includeVariation: true
|
||||
});
|
||||
|
||||
do {
|
||||
if (!options.includeVariation) {
|
||||
previousName = exports.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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of doclet longnames into a tree structure, optionally attaching doclets to the
|
||||
* tree.
|
||||
*
|
||||
* Each level of the tree is an object with the following properties:
|
||||
*
|
||||
* + `longname {string}`: The longname.
|
||||
* + `memberof {string?}`: The memberof.
|
||||
* + `scope {string?}`: The longname's scope, represented as a punctuation mark (for example, `#`
|
||||
* for instance and `.` for static).
|
||||
* + `name {string}`: The short name.
|
||||
* + `doclet {Object?}`: The doclet associated with the longname, or `null` if the doclet was not
|
||||
* provided.
|
||||
* + `children {Object?}`: The children of the current longname. Not present if there are no
|
||||
* children.
|
||||
*
|
||||
* For example, suppose you have the following array of doclet longnames:
|
||||
*
|
||||
* ```js
|
||||
* [
|
||||
* "module:a",
|
||||
* "module:a/b",
|
||||
* "myNamespace",
|
||||
* "myNamespace.Foo",
|
||||
* "myNamespace.Foo#bar"
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* This method converts these longnames to the following tree:
|
||||
*
|
||||
* ```js
|
||||
* {
|
||||
* "module:a": {
|
||||
* "longname": "module:a",
|
||||
* "memberof": "",
|
||||
* "scope": "",
|
||||
* "name": "module:a",
|
||||
* "doclet": null,
|
||||
* "children": {
|
||||
* "/b": {
|
||||
* "longname": "module:a/b",
|
||||
* "memberof": "module:a",
|
||||
* "scope": "/",
|
||||
* "name": "b",
|
||||
* "doclet": null
|
||||
* }
|
||||
* }
|
||||
* },
|
||||
* "myNamespace": {
|
||||
* "longname": "myNamespace",
|
||||
* "memberof": "",
|
||||
* "scope": "",
|
||||
* "name": "myNamespace",
|
||||
* "doclet": null,
|
||||
* "children": {
|
||||
* ".Foo": {
|
||||
* "longname": "myNamespace.Foo",
|
||||
* "memberof": "myNamespace",
|
||||
* "scope": ".",
|
||||
* "name": "Foo",
|
||||
* "doclet": null,
|
||||
* "children": {
|
||||
* "#bar": {
|
||||
* "longname": "myNamespace.Foo#bar",
|
||||
* "memberof": "myNamespace.Foo",
|
||||
* "scope": "#",
|
||||
* "name": "bar",
|
||||
* "doclet": null
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {Array<string>} longnames - The longnames to convert into a tree.
|
||||
* @param {Object<string, module:jsdoc/doclet.Doclet>} doclets - The doclets to attach to a tree.
|
||||
* Each property should be the longname of a doclet, and each value should be the doclet for that
|
||||
* longname.
|
||||
* @return {Object} A tree with information about each longname in the format shown above.
|
||||
*/
|
||||
exports.longnamesToTree = (longnames, doclets) => {
|
||||
const splitOptions = { includeVariation: false };
|
||||
const tree = {};
|
||||
|
||||
longnames.forEach(longname => {
|
||||
let currentLongname = '';
|
||||
let currentParent = tree;
|
||||
let nameInfo;
|
||||
let processed;
|
||||
|
||||
// don't try to add empty longnames to the tree
|
||||
if (!longname) {
|
||||
return;
|
||||
}
|
||||
|
||||
processed = splitLongname(longname, splitOptions);
|
||||
nameInfo = processed.nameInfo;
|
||||
|
||||
processed.chunks.forEach(chunk => {
|
||||
currentLongname += chunk;
|
||||
|
||||
if (currentParent !== tree) {
|
||||
currentParent.children = currentParent.children || {};
|
||||
currentParent = currentParent.children;
|
||||
}
|
||||
|
||||
if (!hasOwnProp.call(currentParent, chunk)) {
|
||||
currentParent[chunk] = nameInfo[currentLongname];
|
||||
}
|
||||
|
||||
if (currentParent[chunk]) {
|
||||
currentParent[chunk].doclet = doclets ? doclets[currentLongname] : null;
|
||||
currentParent = currentParent[chunk];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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) {
|
||||
const buffer = [];
|
||||
let c;
|
||||
let stack = 0;
|
||||
let 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 = 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
|
||||
let 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
|
||||
};
|
||||
};
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* Parse the command line arguments.
|
||||
* @module jsdoc/opts/argparser
|
||||
*/
|
||||
const _ = require('underscore');
|
||||
const util = require('util');
|
||||
|
||||
const hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
|
||||
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) {
|
||||
let max = 0;
|
||||
|
||||
arr.forEach(({length}) => {
|
||||
if (length > max) {
|
||||
max = length;
|
||||
}
|
||||
});
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
function concatWithMaxLength(items, maxLength) {
|
||||
let 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({names, descriptions}) {
|
||||
const MARGIN_LENGTH = 4;
|
||||
const results = [];
|
||||
|
||||
const maxLength = process.stdout.columns;
|
||||
const maxNameLength = findMaxLength(names);
|
||||
const wrapDescriptionAt = maxLength - (MARGIN_LENGTH * 3) - maxNameLength;
|
||||
|
||||
// build the string for each option
|
||||
names.forEach((name, i) => {
|
||||
let result;
|
||||
let partialDescription;
|
||||
let words;
|
||||
|
||||
// add a left margin to the name
|
||||
result = padLeft(names[i], MARGIN_LENGTH);
|
||||
// and a right margin, with extra padding so the descriptions line up with one another
|
||||
result = padRight(result, maxNameLength - names[i].length + MARGIN_LENGTH);
|
||||
|
||||
// split the description on spaces
|
||||
words = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* A parser to interpret the key-value pairs entered on the command line.
|
||||
*
|
||||
* @alias module:jsdoc/opts/argparser
|
||||
*/
|
||||
class ArgParser {
|
||||
/**
|
||||
* Create an instance of the parser.
|
||||
*/
|
||||
constructor() {
|
||||
this._options = [];
|
||||
this._shortNameIndex = {};
|
||||
this._longNameIndex = {};
|
||||
}
|
||||
|
||||
_getOptionByShortName(name) {
|
||||
if (hasOwnProp.call(this._shortNameIndex, name)) {
|
||||
return this._options[this._shortNameIndex[name]];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_getOptionByLongName(name) {
|
||||
if (hasOwnProp.call(this._longNameIndex, name)) {
|
||||
return this._options[this._longNameIndex[name]];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_addOption(option) {
|
||||
let currentIndex;
|
||||
|
||||
const longName = option.longName;
|
||||
const 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.');
|
||||
*/
|
||||
addOption(shortName, longName, hasValue, helpText, canHaveMultiple = false, coercer) {
|
||||
return this._addOption({
|
||||
shortName: shortName,
|
||||
longName: longName,
|
||||
hasValue: hasValue,
|
||||
helpText: helpText,
|
||||
canHaveMultiple: canHaveMultiple,
|
||||
coercer: coercer
|
||||
});
|
||||
}
|
||||
|
||||
// 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`).
|
||||
*/
|
||||
addIgnoredOption(shortName, longName) {
|
||||
return this._addOption({
|
||||
shortName: shortName,
|
||||
longName: longName,
|
||||
ignore: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a summary of all the options with corresponding help text.
|
||||
* @returns {string}
|
||||
*/
|
||||
help() {
|
||||
const options = {
|
||||
names: [],
|
||||
descriptions: []
|
||||
};
|
||||
|
||||
this._options.forEach(option => {
|
||||
let 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.
|
||||
*/
|
||||
parse(args, defaults) {
|
||||
let arg;
|
||||
let next;
|
||||
let option;
|
||||
const result = ( defaults && _.defaults({}, defaults) ) || {};
|
||||
let shortName;
|
||||
let longName;
|
||||
let name;
|
||||
let value;
|
||||
|
||||
result._ = [];
|
||||
for (let i = 0, l = args.length; i < l; i++) {
|
||||
arg = String(args[i]);
|
||||
next = (i < l - 1) ? String(args[i + 1]) : null;
|
||||
shortName = null;
|
||||
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)) {
|
||||
const 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;
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @module jsdoc/opts/args
|
||||
* @requires jsdoc/opts/argparser
|
||||
*/
|
||||
const ArgParser = require('jsdoc/opts/argparser');
|
||||
const cast = require('jsdoc/util/cast').cast;
|
||||
const querystring = require('querystring');
|
||||
|
||||
let ourOptions;
|
||||
|
||||
const argParser = new ArgParser();
|
||||
const hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
|
||||
function parseQuery(str) {
|
||||
return cast( querystring.parse(str) );
|
||||
}
|
||||
|
||||
/* eslint-disable no-multi-spaces */
|
||||
argParser.addOption('a', 'access', true, 'Only display symbols with the given access: "package", public", "protected", "private" or "undefined", or "all" for all access levels. Default: all except "private"', true);
|
||||
argParser.addOption('c', 'configure', true, 'The path to the configuration file. Default: path/to/jsdoc/conf.json');
|
||||
argParser.addOption('d', 'destination', true, 'The path to the output folder. Default: ./out/');
|
||||
argParser.addOption('', 'debug', false, 'Log information for debugging JSDoc.');
|
||||
argParser.addOption('e', 'encoding', true, 'Assume this encoding when reading all source files. Default: utf8');
|
||||
argParser.addOption('h', 'help', false, 'Print this message and quit.');
|
||||
argParser.addOption('', 'match', true, 'When running tests, only use specs whose names contain <value>.', true);
|
||||
argParser.addOption('', 'nocolor', false, 'When running tests, do not use color in console output.');
|
||||
argParser.addOption('p', 'private', false, 'Display symbols marked with the @private tag. Equivalent to "--access all". Default: false');
|
||||
argParser.addOption('P', 'package', true, 'The path to the project\'s package file. Default: path/to/sourcefiles/package.json');
|
||||
argParser.addOption('', 'pedantic', false, 'Treat errors as fatal errors, and treat warnings as errors. Default: false');
|
||||
argParser.addOption('q', 'query', true, 'A query string to parse and store in jsdoc.env.opts.query. Example: foo=bar&baz=true', false, parseQuery);
|
||||
argParser.addOption('r', 'recurse', false, 'Recurse into subdirectories when scanning for source files and tutorials.');
|
||||
argParser.addOption('R', 'readme', true, 'The path to the project\'s README file. Default: path/to/sourcefiles/README.md');
|
||||
argParser.addOption('t', 'template', true, 'The path to the template to use. Default: path/to/jsdoc/templates/default');
|
||||
argParser.addOption('T', 'test', false, 'Run all tests and quit.');
|
||||
argParser.addOption('u', 'tutorials', true, 'Directory in which JSDoc should search for tutorials.');
|
||||
argParser.addOption('v', 'version', false, 'Display the version number and quit.');
|
||||
argParser.addOption('', 'verbose', false, 'Log detailed information to the console as JSDoc runs.');
|
||||
argParser.addOption('X', 'explain', false, 'Dump all found doclet internals to console and quit.');
|
||||
/* eslint-enable no-multi-spaces */
|
||||
|
||||
// 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 = (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 = () => 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 = name => {
|
||||
if (typeof name === 'undefined') {
|
||||
return ourOptions;
|
||||
}
|
||||
else if ( hasOwnProp.call(ourOptions, name) ) {
|
||||
return ourOptions[name];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
const logger = require('jsdoc/util/logger');
|
||||
const stripBom = require('jsdoc/util/stripbom');
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const 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 Package {
|
||||
/**
|
||||
* @param {string} json - The contents of the `package.json` file.
|
||||
*/
|
||||
constructor(json) {
|
||||
let packageInfo;
|
||||
|
||||
/**
|
||||
* The string identifier that is shared by all `Package` objects.
|
||||
*
|
||||
* @readonly
|
||||
* @default
|
||||
* @type {string}
|
||||
*/
|
||||
this.kind = 'package';
|
||||
|
||||
try {
|
||||
packageInfo = JSON.parse(stripBom.strip(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, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.Package = Package;
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Extended version of the standard `path` module.
|
||||
* @module jsdoc/path
|
||||
*/
|
||||
const env = require('jsdoc/env');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function prefixReducer(previousPath, current) {
|
||||
let 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(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 (let 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 = (paths = []) => {
|
||||
let prefix = '';
|
||||
let segments;
|
||||
|
||||
// if there's only one path, its resolved dirname (plus a trailing slash) is the common prefix
|
||||
if (paths.length === 1) {
|
||||
prefix = path.resolve(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 resource in the
|
||||
* following locations, in this order:
|
||||
*
|
||||
* 1. The current working directory
|
||||
* 2. The directory where the JSDoc configuration file is located
|
||||
* 3. The JSDoc directory
|
||||
* 4. Anyplace where `require()` can find the resource (for example, in your project's
|
||||
* `node_modules` directory)
|
||||
*
|
||||
* If the resource path is specified as a fully qualified path, JSDoc searches for the resource in
|
||||
* the following locations, in this order:
|
||||
*
|
||||
* 1. The resource path
|
||||
* 2. Anyplace where `require()` can find the resource (for example, in your project's
|
||||
* `node_modules` directory)
|
||||
*
|
||||
* @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 to the requested resource. Includes the filename if one
|
||||
* was provided.
|
||||
*/
|
||||
exports.getResourcePath = (filepath, filename) => {
|
||||
let result = null;
|
||||
const searchDirs = [env.pwd, path.dirname(env.opts.configure || ''), env.dirname];
|
||||
|
||||
function exists(p) {
|
||||
try {
|
||||
fs.statSync(p);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function resolve(p) {
|
||||
try {
|
||||
return require.resolve(p);
|
||||
}
|
||||
catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function find(p) {
|
||||
// does the requested path exist?
|
||||
if ( exists(p) ) {
|
||||
result = p;
|
||||
}
|
||||
else {
|
||||
// can `require()` find the requested path?
|
||||
result = resolve(p);
|
||||
}
|
||||
|
||||
return Boolean(result);
|
||||
}
|
||||
|
||||
filepath = path.join(filepath, filename || '');
|
||||
|
||||
// is the filepath absolute? if so, just use it
|
||||
if ( path.isAbsolute(filepath) ) {
|
||||
find(filepath);
|
||||
}
|
||||
else {
|
||||
searchDirs.some(searchDir => {
|
||||
if (searchDir) {
|
||||
return find( path.resolve(path.join(searchDir, filepath)) );
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// if we still haven't found the resource, maybe it's an installed module
|
||||
if (!result) {
|
||||
result = resolve(filepath);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Object.keys(path).forEach(member => {
|
||||
exports[member] = path[member];
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Utility functions to support the JSDoc plugin framework.
|
||||
* @module jsdoc/plugins
|
||||
*/
|
||||
const dictionary = require('jsdoc/tag/dictionary');
|
||||
|
||||
function addHandlers(handlers, parser) {
|
||||
Object.keys(handlers).forEach(eventName => {
|
||||
parser.on(eventName, handlers[eventName]);
|
||||
});
|
||||
}
|
||||
|
||||
exports.installPlugins = (plugins, parser) => {
|
||||
let plugin;
|
||||
|
||||
for (let pluginModule of plugins) {
|
||||
plugin = require(pluginModule);
|
||||
|
||||
// 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 Mozilla Parser API node visitor
|
||||
if (plugin.astNodeVisitor) {
|
||||
parser.addAstNodeVisitor(plugin.astNodeVisitor);
|
||||
}
|
||||
}
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Make the contents of a README file available to include in the output.
|
||||
* @module jsdoc/readme
|
||||
*/
|
||||
const env = require('jsdoc/env');
|
||||
const fs = require('jsdoc/fs');
|
||||
const markdown = require('jsdoc/util/markdown');
|
||||
|
||||
/**
|
||||
* Represents a README file.
|
||||
*/
|
||||
class ReadMe {
|
||||
/**
|
||||
* @param {string} path - The filepath to the README.
|
||||
*/
|
||||
constructor(path) {
|
||||
const content = fs.readFileSync(path, env.opts.encoding);
|
||||
const parse = markdown.getParser();
|
||||
|
||||
this.html = parse(content);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReadMe;
|
||||
+664
@@ -0,0 +1,664 @@
|
||||
/**
|
||||
* Schema for validating JSDoc doclets.
|
||||
* @module jsdoc/schema
|
||||
* @see <https://trac.tools.ietf.org/html/draft-wright-json-schema-validation-01>
|
||||
*/
|
||||
// JSON schema types
|
||||
const ARRAY = 'array';
|
||||
const BOOLEAN = 'boolean';
|
||||
const NULL = 'null';
|
||||
const NUMBER = 'number';
|
||||
const OBJECT = 'object';
|
||||
const STRING = 'string';
|
||||
|
||||
const BOOLEAN_OPTIONAL = [BOOLEAN, NULL];
|
||||
const STRING_OPTIONAL = [STRING, NULL];
|
||||
|
||||
const EVENT_REGEXP = 'event:[\\S]+';
|
||||
const PACKAGE_REGEXP = 'package:[\\S]+';
|
||||
|
||||
const STRING_SCHEMA = {
|
||||
type: STRING
|
||||
};
|
||||
|
||||
// information about the code associated with a doclet
|
||||
const META_SCHEMA = exports.META_SCHEMA = {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
code: {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
funcscope: {
|
||||
type: STRING
|
||||
},
|
||||
id: {
|
||||
type: STRING
|
||||
},
|
||||
name: {},
|
||||
node: {
|
||||
type: OBJECT
|
||||
},
|
||||
paramnames: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
type: {
|
||||
type: STRING
|
||||
},
|
||||
value: {}
|
||||
}
|
||||
},
|
||||
columnno: {
|
||||
title: 'The column number of the code associated with this doclet.',
|
||||
type: NUMBER
|
||||
},
|
||||
filename: {
|
||||
title: 'The name of the file that contains the code associated with this doclet.',
|
||||
type: STRING
|
||||
},
|
||||
lineno: {
|
||||
title: 'The line number of the code associated with this doclet.',
|
||||
type: NUMBER
|
||||
},
|
||||
path: {
|
||||
title: 'The path in which the code associated with this doclet is located.',
|
||||
type: STRING
|
||||
},
|
||||
range: {
|
||||
title: 'The positions of the first and last characters of the code associated with ' +
|
||||
'this doclet.',
|
||||
type: ARRAY,
|
||||
minItems: 2,
|
||||
maxItems: 2,
|
||||
items: {
|
||||
type: NUMBER
|
||||
}
|
||||
},
|
||||
vars: {
|
||||
type: OBJECT
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// type property containing type names
|
||||
const 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
|
||||
const ENUM_PROPERTY_SCHEMA = exports.ENUM_PROPERTY_SCHEMA = {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: STRING
|
||||
},
|
||||
defaultvalue: {},
|
||||
description: {
|
||||
type: STRING_OPTIONAL
|
||||
},
|
||||
kind: {
|
||||
type: STRING,
|
||||
enum: ['member']
|
||||
},
|
||||
longname: {
|
||||
type: STRING
|
||||
},
|
||||
memberof: {
|
||||
type: STRING
|
||||
},
|
||||
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,
|
||||
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
|
||||
const PARAM_SCHEMA = exports.PARAM_SCHEMA = {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
// what is the default value for this parameter?
|
||||
defaultvalue: {},
|
||||
// a description of the parameter
|
||||
description: {
|
||||
type: STRING_OPTIONAL
|
||||
},
|
||||
// what name does this parameter have within the function?
|
||||
name: {
|
||||
type: STRING
|
||||
},
|
||||
// can the value for this parameter be null?
|
||||
nullable: {
|
||||
type: BOOLEAN_OPTIONAL
|
||||
},
|
||||
// is a value for this parameter optional?
|
||||
optional: {
|
||||
type: BOOLEAN_OPTIONAL
|
||||
},
|
||||
// what are the types of value expected for this parameter?
|
||||
type: TYPE_PROPERTY_SCHEMA,
|
||||
// can this parameter be repeated?
|
||||
variable: {
|
||||
type: BOOLEAN_OPTIONAL
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const DOCLET_SCHEMA = exports.DOCLET_SCHEMA = {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
// what access privileges are allowed
|
||||
access: {
|
||||
type: STRING,
|
||||
enum: [
|
||||
'package',
|
||||
'private',
|
||||
'protected',
|
||||
'public'
|
||||
]
|
||||
},
|
||||
alias: {
|
||||
type: STRING
|
||||
},
|
||||
async: {
|
||||
type: BOOLEAN
|
||||
},
|
||||
augments: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
author: {
|
||||
type: ARRAY,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
borrowed: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
// name of the target
|
||||
as: {
|
||||
type: STRING
|
||||
},
|
||||
// name of the source
|
||||
from: {
|
||||
type: STRING
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// a description of the class that this constructor belongs to
|
||||
classdesc: {
|
||||
type: STRING
|
||||
},
|
||||
comment: {
|
||||
type: STRING
|
||||
},
|
||||
copyright: {
|
||||
type: STRING
|
||||
},
|
||||
defaultvalue: {},
|
||||
defaultvaluetype: {
|
||||
type: STRING,
|
||||
enum: [OBJECT, ARRAY]
|
||||
},
|
||||
// is usage of this symbol deprecated?
|
||||
deprecated: {
|
||||
type: [STRING, BOOLEAN]
|
||||
},
|
||||
// a description
|
||||
description: {
|
||||
type: STRING_OPTIONAL
|
||||
},
|
||||
// something else to consider
|
||||
examples: {
|
||||
type: ARRAY,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
exceptions: {
|
||||
type: ARRAY,
|
||||
items: PARAM_SCHEMA
|
||||
},
|
||||
// the path to another constructor
|
||||
extends: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
// the path to another doc object
|
||||
fires: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: STRING,
|
||||
pattern: EVENT_REGEXP
|
||||
}
|
||||
},
|
||||
forceMemberof: {
|
||||
type: BOOLEAN_OPTIONAL
|
||||
},
|
||||
generator: {
|
||||
type: BOOLEAN
|
||||
},
|
||||
hideconstructor: {
|
||||
type: BOOLEAN
|
||||
},
|
||||
ignore: {
|
||||
type: BOOLEAN
|
||||
},
|
||||
implementations: {
|
||||
type: ARRAY,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
implements: {
|
||||
type: ARRAY,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
inheritdoc: {
|
||||
type: STRING
|
||||
},
|
||||
inherited: {
|
||||
type: BOOLEAN
|
||||
},
|
||||
inherits: {
|
||||
type: STRING,
|
||||
dependency: {
|
||||
inherited: true
|
||||
}
|
||||
},
|
||||
isEnum: {
|
||||
type: BOOLEAN
|
||||
},
|
||||
// what kind of symbol is this?
|
||||
kind: {
|
||||
type: STRING,
|
||||
enum: [
|
||||
'class',
|
||||
'constant',
|
||||
'event',
|
||||
'external',
|
||||
'file',
|
||||
'function',
|
||||
'interface',
|
||||
'member',
|
||||
'mixin',
|
||||
'module',
|
||||
'namespace',
|
||||
'package',
|
||||
'param',
|
||||
'typedef'
|
||||
]
|
||||
},
|
||||
license: {
|
||||
type: STRING
|
||||
},
|
||||
listens: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: STRING,
|
||||
pattern: EVENT_REGEXP
|
||||
}
|
||||
},
|
||||
longname: {
|
||||
type: STRING
|
||||
},
|
||||
// probably a leading substring of the path
|
||||
memberof: {
|
||||
type: STRING
|
||||
},
|
||||
// information about this doc
|
||||
meta: META_SCHEMA,
|
||||
// was this doclet mixed in?
|
||||
mixed: {
|
||||
type: BOOLEAN
|
||||
},
|
||||
mixes: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
modifies: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
items: PARAM_SCHEMA
|
||||
},
|
||||
// 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
|
||||
},
|
||||
overrides: {
|
||||
type: STRING
|
||||
},
|
||||
// are there function parameters associated with this doc?
|
||||
params: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
items: PARAM_SCHEMA
|
||||
},
|
||||
preserveName: {
|
||||
type: BOOLEAN
|
||||
},
|
||||
properties: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
minItems: 1,
|
||||
items: {
|
||||
anyOf: [ENUM_PROPERTY_SCHEMA, PARAM_SCHEMA]
|
||||
}
|
||||
},
|
||||
readonly: {
|
||||
type: BOOLEAN
|
||||
},
|
||||
// the symbol being documented requires another symbol
|
||||
requires: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
minItems: 1,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
returns: {
|
||||
type: ARRAY,
|
||||
minItems: 1,
|
||||
items: PARAM_SCHEMA
|
||||
},
|
||||
// what sort of parent scope does this symbol have?
|
||||
scope: {
|
||||
type: STRING,
|
||||
enum: [
|
||||
'global',
|
||||
'inner',
|
||||
'instance',
|
||||
'static'
|
||||
]
|
||||
},
|
||||
// something else to consider
|
||||
see: {
|
||||
type: ARRAY,
|
||||
minItems: 1,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
// at what previous version was this doc added?
|
||||
since: {
|
||||
type: STRING
|
||||
},
|
||||
summary: {
|
||||
type: STRING
|
||||
},
|
||||
// arbitrary tags associated with this doc
|
||||
tags: {
|
||||
type: ARRAY,
|
||||
minItems: 1,
|
||||
items: {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
originalTitle: {
|
||||
type: STRING
|
||||
},
|
||||
text: {
|
||||
type: STRING
|
||||
},
|
||||
title: {
|
||||
type: STRING
|
||||
},
|
||||
value: {
|
||||
oneOf: [STRING_SCHEMA, PARAM_SCHEMA]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'this': {
|
||||
type: STRING
|
||||
},
|
||||
todo: {
|
||||
type: ARRAY,
|
||||
minItems: 1,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
// extended tutorials
|
||||
tutorials: {
|
||||
type: ARRAY,
|
||||
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
|
||||
},
|
||||
// can this member be provided more than once? (derived from the type expression)
|
||||
variable: {
|
||||
type: BOOLEAN_OPTIONAL
|
||||
},
|
||||
variation: {
|
||||
type: STRING
|
||||
},
|
||||
// what is the version of this doc
|
||||
version: {
|
||||
type: STRING
|
||||
},
|
||||
// is a member left to be implemented during inheritance?
|
||||
virtual: {
|
||||
type: BOOLEAN
|
||||
},
|
||||
yields: {
|
||||
type: ARRAY,
|
||||
minItems: 1,
|
||||
items: PARAM_SCHEMA
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const CONTACT_INFO_SCHEMA = exports.CONTACT_INFO_SCHEMA = {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
email: {
|
||||
type: STRING
|
||||
},
|
||||
name: {
|
||||
type: STRING
|
||||
},
|
||||
url: {
|
||||
type: STRING,
|
||||
format: 'uri'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const BUGS_SCHEMA = exports.BUGS_SCHEMA = {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
email: {
|
||||
type: STRING
|
||||
},
|
||||
url: {
|
||||
type: STRING,
|
||||
format: 'uri'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const PACKAGE_SCHEMA = exports.PACKAGE_SCHEMA = {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
author: {
|
||||
anyOf: [STRING_SCHEMA, CONTACT_INFO_SCHEMA]
|
||||
},
|
||||
bugs: {
|
||||
anyOf: [STRING_SCHEMA, BUGS_SCHEMA]
|
||||
},
|
||||
contributors: {
|
||||
type: ARRAY,
|
||||
minItems: 0,
|
||||
items: {
|
||||
anyOf: [STRING_SCHEMA, CONTACT_INFO_SCHEMA]
|
||||
}
|
||||
},
|
||||
dependencies: {
|
||||
type: OBJECT
|
||||
},
|
||||
description: {
|
||||
type: STRING
|
||||
},
|
||||
devDependencies: {
|
||||
type: OBJECT
|
||||
},
|
||||
engines: {
|
||||
type: OBJECT
|
||||
},
|
||||
files: {
|
||||
type: ARRAY,
|
||||
uniqueItems: true,
|
||||
minItems: 0,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
homepage: {
|
||||
type: STRING,
|
||||
format: 'uri'
|
||||
},
|
||||
keywords: {
|
||||
type: ARRAY,
|
||||
minItems: 0,
|
||||
items: {
|
||||
type: STRING
|
||||
}
|
||||
},
|
||||
kind: {
|
||||
type: STRING,
|
||||
enum: ['package']
|
||||
},
|
||||
licenses: {
|
||||
type: ARRAY,
|
||||
minItems: 1,
|
||||
items: {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
type: {
|
||||
type: STRING
|
||||
},
|
||||
url: {
|
||||
type: STRING,
|
||||
format: 'uri'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
longname: {
|
||||
type: STRING,
|
||||
pattern: PACKAGE_REGEXP
|
||||
},
|
||||
main: {
|
||||
type: STRING
|
||||
},
|
||||
name: {
|
||||
type: STRING
|
||||
},
|
||||
repository: {
|
||||
type: OBJECT,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
type: {
|
||||
type: STRING
|
||||
},
|
||||
// we don't use `format: 'uri'` here because repo URLs are atypical
|
||||
url: {
|
||||
type: STRING
|
||||
}
|
||||
}
|
||||
},
|
||||
version: {
|
||||
type: STRING
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.DOCLETS_SCHEMA = {
|
||||
type: ARRAY,
|
||||
items: {
|
||||
anyOf: [DOCLET_SCHEMA, PACKAGE_SCHEMA]
|
||||
}
|
||||
};
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
const babelParser = require('@babel/parser');
|
||||
const env = require('jsdoc/env');
|
||||
const logger = require('jsdoc/util/logger');
|
||||
|
||||
// exported so we can use them in tests
|
||||
const parserOptions = exports.parserOptions = {
|
||||
allowAwaitOutsideFunction: true,
|
||||
allowImportExportEverywhere: true,
|
||||
allowReturnOutsideFunction: true,
|
||||
allowSuperOutsideMethod: true,
|
||||
allowUndeclaredExports: true,
|
||||
plugins: [
|
||||
'asyncDoExpressions',
|
||||
'decoratorAutoAccessors',
|
||||
['decorators', {
|
||||
version: '2022-03'
|
||||
}],
|
||||
'decimal',
|
||||
'destructuringPrivate',
|
||||
'doExpressions',
|
||||
'estree',
|
||||
'explicitResourceManagement',
|
||||
'exportDefaultFrom',
|
||||
'functionBind',
|
||||
'functionSent',
|
||||
'importAssertions',
|
||||
'importMeta',
|
||||
'importReflection',
|
||||
'jsx',
|
||||
'moduleBlocks',
|
||||
'partialApplication',
|
||||
['pipelineOperator', {
|
||||
proposal: 'hack',
|
||||
topicToken: '^^'
|
||||
}],
|
||||
'recordAndTuple',
|
||||
'regexpUnicodeSets',
|
||||
'throwExpressions'
|
||||
],
|
||||
ranges: true,
|
||||
sourceType: env.conf.sourceType
|
||||
};
|
||||
|
||||
function parse(source, filename) {
|
||||
let ast;
|
||||
|
||||
try {
|
||||
ast = babelParser.parse(source, parserOptions);
|
||||
// console.log(JSON.stringify(ast, null, 2));
|
||||
}
|
||||
catch (e) {
|
||||
logger.error('Unable to parse %s: %s', filename, e.message);
|
||||
}
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
class AstBuilder {
|
||||
// TODO: docs
|
||||
/* eslint-disable no-empty-function */
|
||||
constructor() {}
|
||||
/* eslint-enable no-empty-function */
|
||||
|
||||
// TODO: docs
|
||||
/* eslint-disable class-methods-use-this */
|
||||
build(source, filename) {
|
||||
return parse(source, filename);
|
||||
}
|
||||
/* eslint-enable class-methods-use-this */
|
||||
}
|
||||
exports.AstBuilder = AstBuilder;
|
||||
+555
@@ -0,0 +1,555 @@
|
||||
// TODO: docs
|
||||
/** @module jsdoc/src/astnode */
|
||||
const cast = require('jsdoc/util/cast').cast;
|
||||
const env = require('jsdoc/env');
|
||||
const name = require('jsdoc/name');
|
||||
const Syntax = require('jsdoc/src/syntax').Syntax;
|
||||
const util = require('util');
|
||||
|
||||
// Counter for generating unique node IDs.
|
||||
let uid = 100000000;
|
||||
|
||||
/**
|
||||
* Check whether an AST node represents a function.
|
||||
*
|
||||
* @alias module:jsdoc/src/astnode.isFunction
|
||||
* @param {(Object|string)} node - The AST node to check, or the `type` property of a node.
|
||||
* @return {boolean} Set to `true` if the node is a function or `false` in all other cases.
|
||||
*/
|
||||
const isFunction = exports.isFunction = node => {
|
||||
let type;
|
||||
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof node === 'string') {
|
||||
type = node;
|
||||
}
|
||||
else {
|
||||
type = node.type;
|
||||
}
|
||||
|
||||
return type === Syntax.FunctionDeclaration || type === Syntax.FunctionExpression ||
|
||||
type === Syntax.MethodDefinition || type === Syntax.ArrowFunctionExpression;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether an AST node creates a new scope.
|
||||
*
|
||||
* @alias module:jsdoc/src/astnode.isScope
|
||||
* @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.
|
||||
*/
|
||||
exports.isScope = node => // TODO: handle blocks with "let" declarations
|
||||
Boolean(node) && typeof node === 'object' && (node.type === Syntax.CatchClause ||
|
||||
node.type === Syntax.ClassDeclaration || node.type === Syntax.ClassExpression || isFunction(node));
|
||||
|
||||
// TODO: docs
|
||||
exports.addNodeProperties = node => {
|
||||
const debugEnabled = Boolean(env.opts.debug);
|
||||
const 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 && typeof node.parentId === 'undefined') {
|
||||
newProperties.parentId = {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return this.parent ? this.parent.nodeId : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (debugEnabled && typeof node.enclosingScopeId === 'undefined') {
|
||||
newProperties.enclosingScopeId = {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return this.enclosingScope ? this.enclosingScope.nodeId : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Object.defineProperties(node, newProperties);
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
const nodeToValue = exports.nodeToValue = node => {
|
||||
let key;
|
||||
let parent;
|
||||
let str;
|
||||
let tempObject;
|
||||
|
||||
switch (node.type) {
|
||||
case Syntax.ArrayExpression:
|
||||
tempObject = [];
|
||||
node.elements.forEach((el, i) => {
|
||||
// handle sparse arrays. use `null` to represent missing values, consistent with
|
||||
// JSON.stringify([,]).
|
||||
if (!el) {
|
||||
tempObject[i] = null;
|
||||
}
|
||||
else {
|
||||
tempObject[i] = nodeToValue(el);
|
||||
}
|
||||
});
|
||||
|
||||
str = JSON.stringify(tempObject);
|
||||
break;
|
||||
|
||||
case Syntax.AssignmentExpression:
|
||||
// falls through
|
||||
|
||||
case Syntax.AssignmentPattern:
|
||||
str = nodeToValue(node.left);
|
||||
break;
|
||||
|
||||
case Syntax.BigIntLiteral:
|
||||
str = node.value;
|
||||
break;
|
||||
|
||||
case Syntax.ClassDeclaration:
|
||||
str = nodeToValue(node.id);
|
||||
break;
|
||||
|
||||
case Syntax.ClassPrivateProperty:
|
||||
// TODO: Strictly speaking, the name should be '#' plus node.key, but because we
|
||||
// already use '#' as scope punctuation, that causes JSDoc to get extremely confused.
|
||||
// The solution probably involves quoting part or all of the name, but JSDoc doesn't
|
||||
// deal with quoted names very nicely right now, and most people probably won't want to
|
||||
// document class private properties anyhow. So for now, we'll just cheat and omit the
|
||||
// leading '#'.
|
||||
str = nodeToValue(node.key.id);
|
||||
break;
|
||||
|
||||
case Syntax.ClassProperty:
|
||||
str = nodeToValue(node.key);
|
||||
break;
|
||||
|
||||
case Syntax.ExportAllDeclaration:
|
||||
// falls through
|
||||
|
||||
case Syntax.ExportDefaultDeclaration:
|
||||
str = 'module.exports';
|
||||
break;
|
||||
|
||||
case Syntax.ExportNamedDeclaration:
|
||||
if (node.declaration) {
|
||||
// like `var` in: export var foo = 'bar';
|
||||
// we need a single value, so we use the first variable name
|
||||
if (node.declaration.declarations) {
|
||||
str = `exports.${nodeToValue(node.declaration.declarations[0])}`;
|
||||
}
|
||||
else {
|
||||
str = `exports.${nodeToValue(node.declaration)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise we'll use the ExportSpecifier nodes
|
||||
break;
|
||||
|
||||
case Syntax.ExportSpecifier:
|
||||
str = `exports.${nodeToValue(node.exported)}`;
|
||||
break;
|
||||
|
||||
case Syntax.ArrowFunctionExpression:
|
||||
// falls through
|
||||
|
||||
case Syntax.FunctionDeclaration:
|
||||
// falls through
|
||||
|
||||
case Syntax.FunctionExpression:
|
||||
if (node.id && node.id.name) {
|
||||
str = node.id.name;
|
||||
}
|
||||
break;
|
||||
|
||||
case Syntax.Identifier:
|
||||
str = node.name;
|
||||
break;
|
||||
|
||||
case Syntax.Literal:
|
||||
str = node.value;
|
||||
break;
|
||||
|
||||
case Syntax.MemberExpression:
|
||||
// could be computed (like foo['bar']) or not (like foo.bar)
|
||||
str = nodeToValue(node.object);
|
||||
if (node.computed) {
|
||||
str += util.format('[%s]', node.property.raw);
|
||||
}
|
||||
else {
|
||||
str += `.${nodeToValue(node.property)}`;
|
||||
}
|
||||
break;
|
||||
|
||||
case Syntax.MethodDefinition:
|
||||
parent = node.parent.parent;
|
||||
// for class expressions, we want the name of the variable the class is assigned to
|
||||
// (but there won't be a name if the class is returned by an arrow function expression)
|
||||
// TODO: we should use `name.LONGNAMES.ANONYMOUS` instead of an empty string, but that
|
||||
// causes problems downstream if the parent class has an `@alias` tag
|
||||
if (parent.type === Syntax.ClassExpression) {
|
||||
str = nodeToValue(parent.parent) || '';
|
||||
}
|
||||
// for the constructor of a module's default export, use a special name
|
||||
else if (node.kind === 'constructor' && parent.parent &&
|
||||
parent.parent.type === Syntax.ExportDefaultDeclaration) {
|
||||
str = 'module.exports';
|
||||
}
|
||||
// for the constructor of a module's named export, use the name of the export
|
||||
// declaration
|
||||
else if (node.kind === 'constructor' && parent.parent &&
|
||||
parent.parent.type === Syntax.ExportNamedDeclaration) {
|
||||
str = nodeToValue(parent.parent);
|
||||
}
|
||||
// for other constructors, use the name of the parent class
|
||||
else if (node.kind === 'constructor') {
|
||||
str = nodeToValue(parent);
|
||||
}
|
||||
// if the method is a member of a module's default export, ignore the name, because it's
|
||||
// irrelevant
|
||||
else if (parent.parent && parent.parent.type === Syntax.ExportDefaultDeclaration) {
|
||||
str = '';
|
||||
}
|
||||
// otherwise, use the class's name
|
||||
else {
|
||||
str = parent.id ? nodeToValue(parent.id) : '';
|
||||
}
|
||||
|
||||
if (node.kind !== 'constructor') {
|
||||
if (str) {
|
||||
str += node.static ? name.SCOPE.PUNC.STATIC : name.SCOPE.PUNC.INSTANCE;
|
||||
}
|
||||
str += nodeToValue(node.key);
|
||||
}
|
||||
break;
|
||||
|
||||
case Syntax.ObjectExpression:
|
||||
tempObject = {};
|
||||
node.properties.forEach(prop => {
|
||||
// ExperimentalSpreadProperty have no key
|
||||
// like var hello = {...hi};
|
||||
if (!prop.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
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] = nodeToValue(prop);
|
||||
}
|
||||
});
|
||||
|
||||
str = JSON.stringify(tempObject);
|
||||
break;
|
||||
|
||||
case Syntax.RestElement:
|
||||
str = nodeToValue(node.argument);
|
||||
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 = nodeToValue(node.argument);
|
||||
|
||||
if (node.prefix === true) {
|
||||
str = cast(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 = nodeToValue(node.id);
|
||||
break;
|
||||
|
||||
default:
|
||||
str = '';
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
// backwards compatibility
|
||||
exports.nodeToString = nodeToValue;
|
||||
|
||||
// TODO: docs
|
||||
const getParamNames = exports.getParamNames = node => {
|
||||
let params;
|
||||
|
||||
if (!node || !node.params) {
|
||||
return [];
|
||||
}
|
||||
|
||||
params = node.params.slice(0);
|
||||
|
||||
return params.map(param => nodeToValue(param));
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
const isAccessor = exports.isAccessor = node => Boolean(node) && typeof node === 'object' &&
|
||||
(node.type === Syntax.Property || node.type === Syntax.MethodDefinition) &&
|
||||
(node.kind === 'get' || node.kind === 'set');
|
||||
|
||||
// TODO: docs
|
||||
exports.isAssignment = node => Boolean(node) && typeof node === 'object' &&
|
||||
(node.type === Syntax.AssignmentExpression || node.type === Syntax.VariableDeclarator);
|
||||
|
||||
// TODO: docs
|
||||
/**
|
||||
* Retrieve information about the node, including its name and type.
|
||||
*/
|
||||
exports.getInfo = node => {
|
||||
const info = {};
|
||||
|
||||
switch (node.type) {
|
||||
// like the function in: "var foo = () => {}"
|
||||
case Syntax.ArrowFunctionExpression:
|
||||
info.node = node;
|
||||
info.name = '';
|
||||
info.type = info.node.type;
|
||||
info.paramnames = getParamNames(node);
|
||||
break;
|
||||
|
||||
// like: "foo = 'bar'" (after declaring foo)
|
||||
// like: "MyClass.prototype.myMethod = function() {}" (after declaring MyClass)
|
||||
case Syntax.AssignmentExpression:
|
||||
info.node = node.right;
|
||||
info.name = nodeToValue(node.left);
|
||||
info.type = info.node.type;
|
||||
info.value = nodeToValue(info.node);
|
||||
// if the assigned value is a function, we need to capture the parameter names here
|
||||
info.paramnames = getParamNames(node.right);
|
||||
break;
|
||||
|
||||
// like "bar='baz'" in: function foo(bar='baz') {}
|
||||
case Syntax.AssignmentPattern:
|
||||
info.node = node;
|
||||
info.name = nodeToValue(node.left);
|
||||
info.type = info.node.type;
|
||||
info.value = nodeToValue(info.node);
|
||||
|
||||
break;
|
||||
|
||||
// like: "class Foo {}"
|
||||
// or "class" in: "export default class {}"
|
||||
case Syntax.ClassDeclaration:
|
||||
info.node = node;
|
||||
// if this class is the default export, we need to use a special name
|
||||
if (node.parent && node.parent.type === Syntax.ExportDefaultDeclaration) {
|
||||
info.name = 'module.exports';
|
||||
}
|
||||
else {
|
||||
info.name = node.id ? nodeToValue(node.id) : '';
|
||||
}
|
||||
info.type = info.node.type;
|
||||
info.paramnames = [];
|
||||
|
||||
node.body.body.some(({kind, value}) => {
|
||||
if (kind === 'constructor') {
|
||||
info.paramnames = getParamNames(value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
// like "#b = 1;" in: "class A { #b = 1; }"
|
||||
case Syntax.ClassPrivateProperty:
|
||||
info.node = node;
|
||||
info.name = nodeToValue(info.node);
|
||||
info.type = info.node.type;
|
||||
break;
|
||||
|
||||
// like "b = 1;" in: "class A { b = 1; }"
|
||||
case Syntax.ClassProperty:
|
||||
info.node = node;
|
||||
info.name = nodeToValue(info.node);
|
||||
info.type = info.node.type;
|
||||
break;
|
||||
|
||||
// like: "export * from 'foo'"
|
||||
case Syntax.ExportAllDeclaration:
|
||||
info.node = node;
|
||||
info.name = nodeToValue(info.node);
|
||||
info.type = info.node.type;
|
||||
break;
|
||||
|
||||
// like: "export default 'foo'"
|
||||
case Syntax.ExportDefaultDeclaration:
|
||||
info.node = node.declaration;
|
||||
info.name = nodeToValue(node);
|
||||
info.type = info.node.type;
|
||||
|
||||
if ( isFunction(info.node) ) {
|
||||
info.paramnames = getParamNames(info.node);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// like: "export var foo;" (has declaration)
|
||||
// or: "export {foo}" (no declaration)
|
||||
case Syntax.ExportNamedDeclaration:
|
||||
info.node = node;
|
||||
info.name = nodeToValue(info.node);
|
||||
info.type = info.node.declaration ? info.node.declaration.type :
|
||||
Syntax.ObjectExpression;
|
||||
|
||||
if (info.node.declaration) {
|
||||
if ( isFunction(info.node.declaration) ) {
|
||||
info.paramnames = getParamNames(info.node.declaration);
|
||||
}
|
||||
|
||||
// TODO: This duplicates logic for another node type in `jsdoc/src/visitor` in
|
||||
// `makeSymbolFoundEvent()`. Is there a way to combine the logic for both node types
|
||||
// into a single module?
|
||||
if (info.node.declaration.kind === 'const') {
|
||||
info.kind = 'constant';
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// like "foo as bar" in: "export {foo as bar}"
|
||||
case Syntax.ExportSpecifier:
|
||||
info.node = node;
|
||||
info.name = nodeToValue(info.node);
|
||||
info.type = info.node.local.type;
|
||||
|
||||
if ( isFunction(info.node.local) ) {
|
||||
info.paramnames = getParamNames(info.node.local);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// like: "function foo() {}"
|
||||
// or the function in: "export default function() {}"
|
||||
case Syntax.FunctionDeclaration:
|
||||
info.node = node;
|
||||
info.name = node.id ? nodeToValue(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 = nodeToValue(info.node);
|
||||
info.type = info.node.type;
|
||||
break;
|
||||
|
||||
// like "a.b.c"
|
||||
case Syntax.MemberExpression:
|
||||
info.node = node;
|
||||
info.name = nodeToValue(info.node);
|
||||
info.type = info.node.type;
|
||||
break;
|
||||
|
||||
// like: "foo() {}"
|
||||
case Syntax.MethodDefinition:
|
||||
info.node = node;
|
||||
info.name = nodeToValue(info.node);
|
||||
info.type = info.node.type;
|
||||
info.paramnames = getParamNames(node.value);
|
||||
break;
|
||||
|
||||
// like "a: 0" in "var foo = {a: 0}"
|
||||
case Syntax.Property:
|
||||
info.node = node.value;
|
||||
info.name = nodeToValue(node.key);
|
||||
info.value = nodeToValue(info.node);
|
||||
|
||||
// property names with unsafe characters must be quoted
|
||||
if ( !/^[$_a-zA-Z0-9]*$/.test(info.name) ) {
|
||||
info.name = `"${String(info.name).replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
|
||||
if ( isAccessor(node) ) {
|
||||
info.type = nodeToValue(info.node);
|
||||
info.paramnames = getParamNames(info.node);
|
||||
}
|
||||
else {
|
||||
info.type = info.node.type;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// like "...bar" in: function foo(...bar) {}
|
||||
case Syntax.RestElement:
|
||||
info.node = node;
|
||||
info.name = nodeToValue(info.node.argument);
|
||||
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 = nodeToValue(info.node);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
info.node = node;
|
||||
info.type = info.node.type;
|
||||
}
|
||||
|
||||
return info;
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @module jsdoc/src/filter
|
||||
*/
|
||||
const env = require('jsdoc/env');
|
||||
const path = require('jsdoc/path');
|
||||
|
||||
function makeRegExp(config) {
|
||||
let regExp = null;
|
||||
|
||||
if (config) {
|
||||
regExp = (typeof config === 'string') ? new RegExp(config) : config;
|
||||
}
|
||||
|
||||
return regExp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alias module:jsdoc/src/filter.Filter
|
||||
*/
|
||||
class Filter {
|
||||
/**
|
||||
* @param {Object} opts
|
||||
* @param {string[]} opts.exclude - Specific files to exclude.
|
||||
* @param {(string|RegExp)} opts.includePattern
|
||||
* @param {(string|RegExp)} opts.excludePattern
|
||||
*/
|
||||
constructor({exclude, includePattern, excludePattern}) {
|
||||
this.exclude = exclude && Array.isArray(exclude) ?
|
||||
exclude.map($ => path.resolve(env.pwd, $)) :
|
||||
null;
|
||||
this.includePattern = makeRegExp(includePattern);
|
||||
this.excludePattern = makeRegExp(excludePattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filepath - The filepath to check.
|
||||
* @returns {boolean} Should the given file be included?
|
||||
*/
|
||||
isIncluded(filepath) {
|
||||
let included = true;
|
||||
|
||||
filepath = path.resolve(env.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(exclude => {
|
||||
if ( filepath.indexOf(exclude) === 0 ) {
|
||||
included = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return included;
|
||||
}
|
||||
}
|
||||
exports.Filter = Filter;
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
/**
|
||||
* @module jsdoc/src/handlers
|
||||
*/
|
||||
const escape = require('escape-string-regexp');
|
||||
const jsdoc = {
|
||||
doclet: require('jsdoc/doclet'),
|
||||
name: require('jsdoc/name'),
|
||||
src: {
|
||||
syntax: require('jsdoc/src/syntax')
|
||||
},
|
||||
util: {
|
||||
logger: require('jsdoc/util/logger')
|
||||
}
|
||||
};
|
||||
|
||||
let currentModule = null;
|
||||
const SCOPE_NAMES = jsdoc.name.SCOPE.NAMES;
|
||||
const SCOPE_PUNC = jsdoc.name.SCOPE.PUNC;
|
||||
const Syntax = jsdoc.src.syntax.Syntax;
|
||||
|
||||
class CurrentModule {
|
||||
constructor(doclet) {
|
||||
this.doclet = doclet;
|
||||
this.longname = doclet.longname;
|
||||
this.originalName = doclet.meta.code.name || '';
|
||||
}
|
||||
}
|
||||
function filterByLongname({longname}) {
|
||||
// you can't document prototypes
|
||||
if ( /#$/.test(longname) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function createDoclet(comment, e) {
|
||||
let doclet;
|
||||
let flatComment;
|
||||
let msg;
|
||||
|
||||
try {
|
||||
doclet = new jsdoc.doclet.Doclet(comment, e);
|
||||
}
|
||||
catch (error) {
|
||||
flatComment = comment.replace(/[\r\n]/g, '');
|
||||
msg = `cannot create a doclet for the comment "${flatComment}": ${error.message}`;
|
||||
jsdoc.util.logger.error(msg);
|
||||
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) {
|
||||
let 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(parser, doclet) {
|
||||
let parentDoclet;
|
||||
let skipMemberof;
|
||||
|
||||
// handle module symbols that are _not_ assigned to module.exports
|
||||
if (currentModule && currentModule.longname !== doclet.name) {
|
||||
if (!doclet.scope) {
|
||||
// is this a method definition? if so, we usually get the scope from the node directly
|
||||
if (doclet.meta && doclet.meta.code && doclet.meta.code.node &&
|
||||
doclet.meta.code.node.type === Syntax.MethodDefinition) {
|
||||
// special case for constructors of classes that have @alias tags
|
||||
if (doclet.meta.code.node.kind === 'constructor') {
|
||||
parentDoclet = parser._getDocletById(
|
||||
doclet.meta.code.node.parent.parent.nodeId
|
||||
);
|
||||
|
||||
if (parentDoclet && parentDoclet.alias) {
|
||||
// the constructor should use the same name as the class
|
||||
doclet.addTag('alias', parentDoclet.alias);
|
||||
doclet.addTag('name', parentDoclet.alias);
|
||||
|
||||
// and we shouldn't try to set a memberof value
|
||||
skipMemberof = true;
|
||||
}
|
||||
}
|
||||
else if (doclet.meta.code.node.static) {
|
||||
doclet.addTag('static');
|
||||
}
|
||||
else {
|
||||
doclet.addTag('instance');
|
||||
}
|
||||
}
|
||||
// is this something that the module exports? if so, it's a static member
|
||||
else if (doclet.meta && doclet.meta.code && doclet.meta.code.node &&
|
||||
doclet.meta.code.node.parent &&
|
||||
doclet.meta.code.node.parent.type === Syntax.ExportNamedDeclaration) {
|
||||
doclet.addTag('static');
|
||||
}
|
||||
// otherwise, it must be an inner member
|
||||
else {
|
||||
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 (unless we were told to skip adding memberof)
|
||||
if (!doclet.memberof && doclet.scope !== SCOPE_NAMES.GLOBAL && !skipMemberof) {
|
||||
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) {
|
||||
let 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) {
|
||||
let 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) {
|
||||
let memberof = '';
|
||||
let nameAndPunc;
|
||||
let scopePunc = '';
|
||||
|
||||
// handle computed properties like foo['bar']
|
||||
if (trailingPunc === '[') {
|
||||
// we don't know yet whether the symbol is a static or instance member
|
||||
trailingPunc = null;
|
||||
}
|
||||
|
||||
nameAndPunc = nameStartsWith + (trailingPunc || '');
|
||||
|
||||
// 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) {
|
||||
let basename;
|
||||
let memberof;
|
||||
let memberofInfo;
|
||||
let moduleOriginalName = '';
|
||||
let resolveTargetRegExp;
|
||||
let scopePunc;
|
||||
let 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);
|
||||
basename = memberofInfo.basename;
|
||||
memberof = memberofInfo.memberof;
|
||||
}
|
||||
|
||||
// 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(parser, doclet);
|
||||
}
|
||||
}
|
||||
|
||||
function newSymbolDoclet(parser, docletSrc, e) {
|
||||
const 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 && typeof e.code.name !== 'undefined' && 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;
|
||||
}
|
||||
|
||||
// handle cases where the doclet kind is auto-detected from the node type
|
||||
if (e.code.kind && newDoclet.kind === 'member') {
|
||||
newDoclet.kind = e.code.kind;
|
||||
}
|
||||
|
||||
addDoclet(parser, newDoclet);
|
||||
e.doclet = newDoclet;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach these event handlers to a particular instance of a parser.
|
||||
* @param parser
|
||||
*/
|
||||
exports.attachTo = 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', e => {
|
||||
const comments = e.comment.split(/@also\b/g);
|
||||
let newDoclet;
|
||||
|
||||
for (let 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(parser, 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', e => {
|
||||
const comments = e.comment.split(/@also\b/g);
|
||||
|
||||
for (let i = 0, l = comments.length; i < l; i++) {
|
||||
newSymbolDoclet(parser, comments[i], e);
|
||||
}
|
||||
});
|
||||
|
||||
parser.on('fileComplete', () => {
|
||||
currentModule = null;
|
||||
});
|
||||
};
|
||||
+673
@@ -0,0 +1,673 @@
|
||||
/**
|
||||
* @module jsdoc/src/parser
|
||||
*/
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const fs = require('jsdoc/fs');
|
||||
const jsdoc = {
|
||||
doclet: require('jsdoc/doclet'),
|
||||
env: require('jsdoc/env'),
|
||||
name: require('jsdoc/name'),
|
||||
src: {
|
||||
astnode: require('jsdoc/src/astnode'),
|
||||
syntax: require('jsdoc/src/syntax')
|
||||
},
|
||||
util: {
|
||||
doop: require('jsdoc/util/doop')
|
||||
}
|
||||
};
|
||||
const logger = require('jsdoc/util/logger');
|
||||
|
||||
const hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
const Syntax = jsdoc.src.syntax.Syntax;
|
||||
|
||||
// TODO: docs
|
||||
const PARSERS = exports.PARSERS = {
|
||||
js: 'jsdoc/src/parser'
|
||||
};
|
||||
/* eslint-disable no-script-url */
|
||||
// Prefix for JavaScript strings that were provided in lieu of a filename.
|
||||
const SCHEMA = 'javascript:';
|
||||
/* eslint-enable no-script-url */
|
||||
|
||||
class DocletCache {
|
||||
constructor() {
|
||||
this._doclets = {};
|
||||
}
|
||||
|
||||
get(name) {
|
||||
if ( !hasOwnProp.call(this._doclets, name) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// always return the most recent doclet
|
||||
return this._doclets[name][this._doclets[name].length - 1];
|
||||
}
|
||||
|
||||
put(name, value) {
|
||||
if ( !hasOwnProp.call(this._doclets, name) ) {
|
||||
this._doclets[name] = [];
|
||||
}
|
||||
|
||||
this._doclets[name].push(value);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
exports.createParser = type => {
|
||||
let modulePath;
|
||||
|
||||
if (!type) {
|
||||
/* istanbul ignore next */
|
||||
type = 'js';
|
||||
}
|
||||
|
||||
if (hasOwnProp.call(PARSERS, type)) {
|
||||
modulePath = PARSERS[type];
|
||||
}
|
||||
else {
|
||||
logger.fatal('The parser type "%s" is not recognized.', type);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return new (require(modulePath).Parser)();
|
||||
};
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
function definedInScope(doclet, basename) {
|
||||
return Boolean(doclet) && Boolean(doclet.meta) && Boolean(doclet.meta.vars) &&
|
||||
Boolean(basename) && hasOwnProp.call(doclet.meta.vars, basename);
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
/**
|
||||
* @alias module:jsdoc/src/parser.Parser
|
||||
* @extends module:events.EventEmitter
|
||||
*/
|
||||
class Parser extends EventEmitter {
|
||||
// TODO: docs
|
||||
constructor(builderInstance, visitorInstance, walkerInstance) {
|
||||
super();
|
||||
|
||||
this.clear();
|
||||
|
||||
this._astBuilder = builderInstance || new (require('jsdoc/src/astbuilder').AstBuilder)();
|
||||
this._visitor = visitorInstance || new (require('jsdoc/src/visitor').Visitor)();
|
||||
this._walker = walkerInstance || new (require('jsdoc/src/walker').Walker)();
|
||||
|
||||
this._visitor.setParser(this);
|
||||
|
||||
Object.defineProperties(this, {
|
||||
astBuilder: {
|
||||
get() {
|
||||
return this._astBuilder;
|
||||
}
|
||||
},
|
||||
visitor: {
|
||||
get() {
|
||||
return this._visitor;
|
||||
}
|
||||
},
|
||||
walker: {
|
||||
get() {
|
||||
return this._walker;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
clear() {
|
||||
this._resultBuffer = [];
|
||||
this._resultBuffer.index = {
|
||||
borrowed: [],
|
||||
documented: {},
|
||||
longname: {},
|
||||
memberof: {}
|
||||
};
|
||||
this._byNodeId = new DocletCache();
|
||||
this._byLongname = new DocletCache();
|
||||
this._byLongname.put(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]
|
||||
*
|
||||
* @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);
|
||||
*/
|
||||
parse(sourceFiles, encoding) {
|
||||
encoding = encoding || jsdoc.env.conf.encoding || 'utf8';
|
||||
|
||||
let filename = '';
|
||||
let sourceCode = '';
|
||||
let sourceFile;
|
||||
const parsedFiles = [];
|
||||
const e = {};
|
||||
|
||||
if (typeof sourceFiles === 'string') {
|
||||
sourceFiles = [sourceFiles];
|
||||
}
|
||||
|
||||
e.sourcefiles = sourceFiles;
|
||||
logger.debug('Parsing source files: %j', sourceFiles);
|
||||
|
||||
this.emit('parseBegin', e);
|
||||
|
||||
for (let i = 0, l = sourceFiles.length; i < l; i++) {
|
||||
sourceCode = '';
|
||||
sourceFile = sourceFiles[i];
|
||||
|
||||
if (sourceFile.indexOf(SCHEMA) === 0) {
|
||||
sourceCode = sourceFile.substr(SCHEMA.length);
|
||||
filename = `[[string${i}]]`;
|
||||
}
|
||||
else {
|
||||
filename = sourceFile;
|
||||
try {
|
||||
sourceCode = fs.readFileSync(filename, encoding);
|
||||
}
|
||||
catch (err) {
|
||||
logger.error('Unable to read and parse the source file %s: %s', filename, err);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
fireProcessingComplete(doclets) {
|
||||
this.emit('processingComplete', { doclets: doclets });
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
results() {
|
||||
return this._resultBuffer;
|
||||
}
|
||||
|
||||
// TODO: update docs
|
||||
/**
|
||||
* @param {module:jsdoc/doclet.Doclet} doclet The parse result to add to the result buffer.
|
||||
*/
|
||||
addResult(doclet) {
|
||||
const index = this._resultBuffer.index;
|
||||
|
||||
this._resultBuffer.push(doclet);
|
||||
|
||||
// track all doclets by longname
|
||||
if ( !hasOwnProp.call(index.longname, doclet.longname) ) {
|
||||
index.longname[doclet.longname] = [];
|
||||
}
|
||||
index.longname[doclet.longname].push(doclet);
|
||||
|
||||
// track all doclets that have a memberof by memberof
|
||||
if (doclet.memberof) {
|
||||
if ( !hasOwnProp.call(index.memberof, doclet.memberof) ) {
|
||||
index.memberof[doclet.memberof] = [];
|
||||
}
|
||||
index.memberof[doclet.memberof].push(doclet);
|
||||
}
|
||||
|
||||
// track longnames of documented symbols
|
||||
if (!doclet.undocumented) {
|
||||
if ( !hasOwnProp.call(index.documented, doclet.longname) ) {
|
||||
index.documented[doclet.longname] = [];
|
||||
}
|
||||
index.documented[doclet.longname].push(doclet);
|
||||
}
|
||||
|
||||
// track doclets with a `borrowed` property
|
||||
if ( hasOwnProp.call(doclet, 'borrowed') ) {
|
||||
index.borrowed.push(doclet);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
addAstNodeVisitor(visitor) {
|
||||
this._visitor.addAstNodeVisitor(visitor);
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
getAstNodeVisitors() {
|
||||
return this._visitor.getAstNodeVisitors();
|
||||
}
|
||||
|
||||
/** @private */
|
||||
_parseSourceCode(sourceCode, sourceName) {
|
||||
let ast;
|
||||
let e = {
|
||||
filename: sourceName
|
||||
};
|
||||
|
||||
this.emit('fileBegin', e);
|
||||
logger.info('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);
|
||||
}
|
||||
|
||||
/** @private */
|
||||
_walkAst(ast, visitor, sourceName) {
|
||||
this._walker.recurse(ast, visitor, sourceName);
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
addDocletRef(e) {
|
||||
let fakeDoclet;
|
||||
let node;
|
||||
|
||||
if (e && e.code && e.code.node) {
|
||||
node = e.code.node;
|
||||
if (e.doclet) {
|
||||
// allow lookup from node ID => doclet
|
||||
this._byNodeId.put(node.nodeId, e.doclet);
|
||||
this._byLongname.put(e.doclet.longname, 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 ||
|
||||
node.type === Syntax.ArrowFunctionExpression) &&
|
||||
!this._getDocletById(node.nodeId) ) {
|
||||
fakeDoclet = {
|
||||
longname: jsdoc.name.LONGNAMES.ANONYMOUS,
|
||||
meta: {
|
||||
code: e.code
|
||||
}
|
||||
};
|
||||
this._byNodeId.put(node.nodeId, fakeDoclet);
|
||||
this._byLongname.put(fakeDoclet.longname, fakeDoclet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
_getDocletById(id) {
|
||||
return this._byNodeId.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the most recently seen doclet that has the given longname.
|
||||
*
|
||||
* @param {string} longname - The longname to search for.
|
||||
* @return {module:jsdoc/doclet.Doclet?} The most recent doclet for the longname.
|
||||
*/
|
||||
_getDocletByLongname(longname) {
|
||||
return this._byLongname.get(longname);
|
||||
}
|
||||
|
||||
// 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.
|
||||
*/
|
||||
astnodeToMemberof(node) {
|
||||
let basename;
|
||||
let doclet;
|
||||
let scope;
|
||||
|
||||
const result = {};
|
||||
const type = node.type;
|
||||
|
||||
if ( (type === Syntax.FunctionDeclaration || type === Syntax.FunctionExpression ||
|
||||
type === Syntax.ArrowFunctionExpression || type === Syntax.VariableDeclarator) &&
|
||||
node.enclosingScope ) {
|
||||
doclet = this._getDocletById(node.enclosingScope.nodeId);
|
||||
|
||||
if (!doclet) {
|
||||
result.memberof = jsdoc.name.LONGNAMES.ANONYMOUS + jsdoc.name.SCOPE.PUNC.INNER;
|
||||
}
|
||||
else {
|
||||
result.memberof = doclet.longname + jsdoc.name.SCOPE.PUNC.INNER;
|
||||
}
|
||||
}
|
||||
else if (type === Syntax.ClassPrivateProperty || type === Syntax.ClassProperty) {
|
||||
doclet = this._getDocletById(node.enclosingScope.nodeId);
|
||||
|
||||
if (!doclet) {
|
||||
result.memberof = jsdoc.name.LONGNAMES.ANONYMOUS + jsdoc.name.SCOPE.PUNC.INSTANCE;
|
||||
}
|
||||
else {
|
||||
result.memberof = doclet.longname + jsdoc.name.SCOPE.PUNC.INSTANCE;
|
||||
}
|
||||
}
|
||||
else if (type === Syntax.MethodDefinition && node.kind === 'constructor') {
|
||||
doclet = this._getDocletById(node.enclosingScope.nodeId);
|
||||
|
||||
// global classes aren't a member of anything
|
||||
if (doclet.memberof) {
|
||||
result.memberof = doclet.memberof + jsdoc.name.SCOPE.PUNC.INNER;
|
||||
}
|
||||
}
|
||||
// special case for methods in classes that are returned by arrow function expressions; for
|
||||
// other method definitions, we get the memberof from the node name elsewhere. yes, this is
|
||||
// confusing...
|
||||
else if (type === Syntax.MethodDefinition && node.parent.parent.parent &&
|
||||
node.parent.parent.parent.type === Syntax.ArrowFunctionExpression) {
|
||||
doclet = this._getDocletById(node.enclosingScope.nodeId);
|
||||
|
||||
if (doclet) {
|
||||
result.memberof = doclet.longname +
|
||||
(node.static === true ?
|
||||
jsdoc.name.SCOPE.PUNC.STATIC :
|
||||
jsdoc.name.SCOPE.PUNC.INSTANCE);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// check local references for aliases
|
||||
scope = node;
|
||||
basename = jsdoc.name.getBasename( jsdoc.src.astnode.nodeToValue(node) );
|
||||
|
||||
// walk up the scope chain until we find the scope in which the node is defined
|
||||
while (scope.enclosingScope) {
|
||||
doclet = this._getDocletById(scope.enclosingScope.nodeId);
|
||||
if ( doclet && definedInScope(doclet, basename) ) {
|
||||
result.memberof = doclet.meta.vars[basename];
|
||||
result.basename = basename;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// move up
|
||||
scope = scope.enclosingScope;
|
||||
}
|
||||
}
|
||||
|
||||
// do we know that it's a global?
|
||||
doclet = this._getDocletByLongname(jsdoc.name.LONGNAMES.GLOBAL);
|
||||
if ( doclet && definedInScope(doclet, basename) ) {
|
||||
result.memberof = doclet.meta.vars[basename];
|
||||
result.basename = basename;
|
||||
}
|
||||
else {
|
||||
doclet = this._getDocletById(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.memberof = doclet.longname || doclet.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the doclet for the lowest-level class, if any, that is in the scope chain for a given node.
|
||||
*
|
||||
* @param {Object} node - The node whose scope chain will be searched.
|
||||
* @return {module:jsdoc/doclet.Doclet?} The doclet for the lowest-level class in the node's scope
|
||||
* chain.
|
||||
*/
|
||||
_getParentClass({enclosingScope}) {
|
||||
let doclet;
|
||||
let nameAtoms;
|
||||
let scope = enclosingScope;
|
||||
|
||||
function isClass(d) {
|
||||
return d && d.kind === 'class';
|
||||
}
|
||||
|
||||
while (scope) {
|
||||
// get the doclet, if any, for the parent scope
|
||||
doclet = this._getDocletById(scope.nodeId);
|
||||
|
||||
if (doclet) {
|
||||
// is the doclet for a class? if so, we're done
|
||||
if ( isClass(doclet) ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// is the doclet for an instance member of a class? if so, try to get the doclet for the
|
||||
// owning class
|
||||
nameAtoms = jsdoc.name.shorten(doclet.longname);
|
||||
if (nameAtoms.scope === jsdoc.name.SCOPE.PUNC.INSTANCE) {
|
||||
doclet = this._getDocletByLongname(nameAtoms.memberof);
|
||||
if ( isClass(doclet) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move up to the next parent scope
|
||||
scope = scope.enclosingScope;
|
||||
}
|
||||
|
||||
return (isClass(doclet) ? doclet : null);
|
||||
}
|
||||
|
||||
// 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.
|
||||
*/
|
||||
resolveThis(node) {
|
||||
let doclet;
|
||||
let parentClass;
|
||||
let result;
|
||||
|
||||
// Properties are handled below.
|
||||
if (node.type !== Syntax.Property && node.enclosingScope) {
|
||||
// For ES2015 constructor functions, we use the class declaration to resolve `this`.
|
||||
if (node.parent && node.parent.type === Syntax.MethodDefinition &&
|
||||
node.parent.kind === 'constructor') {
|
||||
doclet = this._getDocletById(node.parent.parent.parent.nodeId);
|
||||
}
|
||||
// Otherwise, if there's an enclosing scope, we use the enclosing scope to resolve `this`.
|
||||
else {
|
||||
doclet = this._getDocletById(node.enclosingScope.nodeId);
|
||||
}
|
||||
|
||||
if (!doclet) {
|
||||
result = jsdoc.name.LONGNAMES.ANONYMOUS; // TODO handle global this?
|
||||
}
|
||||
else if (doclet.this) {
|
||||
result = doclet.this;
|
||||
}
|
||||
else if (doclet.kind === 'function' && doclet.memberof) {
|
||||
parentClass = this._getParentClass(node);
|
||||
|
||||
// like: function Foo() { this.bar = function(n) { /** blah */ this.name = n; };
|
||||
// or: Foo.prototype.bar = function(n) { /** blah */ this.name = n; };
|
||||
// or: var Foo = exports.Foo = function(n) { /** blah */ this.name = n; };
|
||||
// or: Foo.constructor = function(n) { /** blah */ this.name = n; }
|
||||
if ( parentClass || /\.constructor$/.test(doclet.longname) ) {
|
||||
result = doclet.memberof;
|
||||
}
|
||||
// like: function notAClass(n) { /** global this */ this.name = n; }
|
||||
else {
|
||||
result = doclet.longname;
|
||||
}
|
||||
}
|
||||
// like: var foo = function(n) { /** blah */ this.bar = n; }
|
||||
else if ( doclet.kind === 'member' && jsdoc.src.astnode.isAssignment(node) ) {
|
||||
result = doclet.longname;
|
||||
}
|
||||
// walk up to the closest class we can find
|
||||
else if (doclet.kind === 'class' || doclet.kind === 'interface' || doclet.kind === 'module') {
|
||||
result = doclet.longname;
|
||||
}
|
||||
else if (node.enclosingScope) {
|
||||
result = this.resolveThis(node.enclosingScope);
|
||||
}
|
||||
}
|
||||
// For object properties, we use the node's parent (the object) instead.
|
||||
else {
|
||||
doclet = this._getDocletById(node.parent.nodeId);
|
||||
|
||||
if (!doclet) {
|
||||
// The object wasn't documented, so we don't know what name to use.
|
||||
result = '';
|
||||
}
|
||||
else {
|
||||
result = doclet.longname;
|
||||
}
|
||||
}
|
||||
|
||||
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.<module:jsdoc/doclet.Doclet>} An array of doclets for the parent object or objects, or
|
||||
* an empty array if no doclets are found.
|
||||
*/
|
||||
resolvePropertyParents({parent}) {
|
||||
let currentAncestor = parent;
|
||||
let nextAncestor = currentAncestor.parent;
|
||||
let doclet;
|
||||
const doclets = [];
|
||||
|
||||
while (currentAncestor) {
|
||||
doclet = this._getDocletById(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.
|
||||
*/
|
||||
resolveVar({enclosingScope, type}, basename) {
|
||||
let doclet;
|
||||
let result;
|
||||
const scope = enclosingScope;
|
||||
|
||||
// HACK: return an empty string for function declarations so they don't end up in anonymous
|
||||
// scope (see #685 and #693)
|
||||
if (type === Syntax.FunctionDeclaration) {
|
||||
result = '';
|
||||
}
|
||||
else if (!scope) {
|
||||
result = ''; // global
|
||||
}
|
||||
else {
|
||||
doclet = this._getDocletById(scope.nodeId);
|
||||
if ( definedInScope(doclet, basename) ) {
|
||||
result = doclet.longname;
|
||||
}
|
||||
else {
|
||||
result = this.resolveVar(scope, basename);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
resolveEnum(e) {
|
||||
const doclets = this.resolvePropertyParents(e.code.node.parent);
|
||||
|
||||
doclets.forEach(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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.Parser = Parser;
|
||||
|
||||
// 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 {number} columnno The column number associated with the found comment.
|
||||
* @property {string} filename The file name associated with the found comment.
|
||||
*/
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @module jsdoc/src/scanner
|
||||
* @requires module:jsdoc/fs
|
||||
*/
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const env = require('jsdoc/env');
|
||||
const fs = require('jsdoc/fs');
|
||||
const logger = require('jsdoc/util/logger');
|
||||
const path = require('jsdoc/path');
|
||||
|
||||
/**
|
||||
* @extends module:events.EventEmitter
|
||||
*/
|
||||
class Scanner extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively searches the given searchPaths for js files.
|
||||
* @param {Array.<string>} searchPaths
|
||||
* @param {number} [depth]
|
||||
* @fires sourceFileFound
|
||||
*/
|
||||
scan(searchPaths = [], depth = 1, filter) {
|
||||
let currentFile;
|
||||
let filePaths = [];
|
||||
|
||||
searchPaths.forEach($ => {
|
||||
const filepath = path.resolve( env.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($ => filter.isIncluded($));
|
||||
|
||||
filePaths = filePaths.filter($ => {
|
||||
const e = { fileName: $ };
|
||||
|
||||
this.emit('sourceFileFound', e);
|
||||
|
||||
return !e.defaultPrevented;
|
||||
});
|
||||
|
||||
return filePaths;
|
||||
}
|
||||
}
|
||||
exports.Scanner = Scanner;
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// TODO: docs
|
||||
exports.Syntax = {
|
||||
ArrayExpression: 'ArrayExpression',
|
||||
ArrayPattern: 'ArrayPattern',
|
||||
ArrowFunctionExpression: 'ArrowFunctionExpression',
|
||||
AssignmentExpression: 'AssignmentExpression',
|
||||
AssignmentPattern: 'AssignmentPattern',
|
||||
AwaitExpression: 'AwaitExpression',
|
||||
BigIntLiteral: 'BigIntLiteral',
|
||||
BinaryExpression: 'BinaryExpression',
|
||||
BindExpression: 'BindExpression',
|
||||
BlockStatement: 'BlockStatement',
|
||||
BreakStatement: 'BreakStatement',
|
||||
CallExpression: 'CallExpression',
|
||||
CatchClause: 'CatchClause',
|
||||
ClassBody: 'ClassBody',
|
||||
ClassDeclaration: 'ClassDeclaration',
|
||||
ClassExpression: 'ClassExpression',
|
||||
ClassPrivateProperty: 'ClassPrivateProperty',
|
||||
ClassProperty: 'ClassProperty',
|
||||
ComprehensionBlock: 'ComprehensionBlock',
|
||||
ComprehensionExpression: 'ComprehensionExpression',
|
||||
ConditionalExpression: 'ConditionalExpression',
|
||||
ContinueStatement: 'ContinueStatement',
|
||||
DebuggerStatement: 'DebuggerStatement',
|
||||
Decorator: 'Decorator',
|
||||
DoExpression: 'DoExpression',
|
||||
DoWhileStatement: 'DoWhileStatement',
|
||||
EmptyStatement: 'EmptyStatement',
|
||||
ExperimentalRestProperty: 'ExperimentalRestProperty',
|
||||
ExperimentalSpreadProperty: 'ExperimentalSpreadProperty',
|
||||
ExportAllDeclaration: 'ExportAllDeclaration',
|
||||
ExportDefaultDeclaration: 'ExportDefaultDeclaration',
|
||||
ExportDefaultSpecifier: 'ExportDefaultSpecifier',
|
||||
ExportNamedDeclaration: 'ExportNamedDeclaration',
|
||||
ExportNamespaceSpecifier: 'ExportNamespaceSpecifier',
|
||||
ExportSpecifier: 'ExportSpecifier',
|
||||
ExpressionStatement: 'ExpressionStatement',
|
||||
File: 'File',
|
||||
ForInStatement: 'ForInStatement',
|
||||
ForOfStatement: 'ForOfStatement',
|
||||
ForStatement: 'ForStatement',
|
||||
FunctionDeclaration: 'FunctionDeclaration',
|
||||
FunctionExpression: 'FunctionExpression',
|
||||
Identifier: 'Identifier',
|
||||
IfStatement: 'IfStatement',
|
||||
Import: 'Import',
|
||||
ImportDeclaration: 'ImportDeclaration',
|
||||
ImportDefaultSpecifier: 'ImportDefaultSpecifier',
|
||||
ImportNamespaceSpecifier: 'ImportNamespaceSpecifier',
|
||||
ImportSpecifier: 'ImportSpecifier',
|
||||
JSXAttribute: 'JSXAttribute',
|
||||
JSXClosingElement: 'JSXClosingElement',
|
||||
JSXElement: 'JSXElement',
|
||||
JSXEmptyExpression: 'JSXEmptyExpression',
|
||||
JSXExpressionContainer: 'JSXExpressionContainer',
|
||||
JSXIdentifier: 'JSXIdentifier',
|
||||
JSXMemberExpression: 'JSXMemberExpression',
|
||||
JSXNamespacedName: 'JSXNamespacedName',
|
||||
JSXOpeningElement: 'JSXOpeningElement',
|
||||
JSXSpreadAttribute: 'JSXSpreadAttribute',
|
||||
JSXText: 'JSXText',
|
||||
LabeledStatement: 'LabeledStatement',
|
||||
LetStatement: 'LetStatement',
|
||||
Literal: 'Literal',
|
||||
LogicalExpression: 'LogicalExpression',
|
||||
MemberExpression: 'MemberExpression',
|
||||
MetaProperty: 'MetaProperty',
|
||||
MethodDefinition: 'MethodDefinition',
|
||||
NewExpression: 'NewExpression',
|
||||
ObjectExpression: 'ObjectExpression',
|
||||
ObjectPattern: 'ObjectPattern',
|
||||
PrivateName: 'PrivateName',
|
||||
Program: 'Program',
|
||||
Property: 'Property',
|
||||
RestElement: 'RestElement',
|
||||
ReturnStatement: 'ReturnStatement',
|
||||
SequenceExpression: 'SequenceExpression',
|
||||
SpreadElement: 'SpreadElement',
|
||||
Super: 'Super',
|
||||
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'
|
||||
};
|
||||
+859
@@ -0,0 +1,859 @@
|
||||
/**
|
||||
* @module jsdoc/src/visitor
|
||||
*/
|
||||
// TODO: consider exporting more stuff so users can override it
|
||||
|
||||
const 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')
|
||||
}
|
||||
};
|
||||
|
||||
const Syntax = jsdoc.src.syntax.Syntax;
|
||||
|
||||
/**
|
||||
* Get the raw comment string for a block comment node.
|
||||
*
|
||||
* @private
|
||||
* @param {!Object} comment - A comment node with `type` and `value` properties.
|
||||
*/
|
||||
function getRawComment({value}) {
|
||||
return `/*${value}*/`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a comment node represents a block comment.
|
||||
*
|
||||
* @param {!Object} comment - A comment node with `type` and `value` properties.
|
||||
* @return {boolean} `true` if the comment is a block comment, `false` otherwise.
|
||||
*/
|
||||
function isBlockComment({type}) {
|
||||
return type === 'CommentBlock';
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a block comment exists; that it is a JSDoc comment; 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.length > 4 && commentSrc.indexOf('/**') === 0 &&
|
||||
commentSrc.indexOf('/***') !== 0;
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
function getLeadingJsdocComment(node) {
|
||||
let comment = null;
|
||||
let leadingComments = node.leadingComments;
|
||||
|
||||
if (Array.isArray(leadingComments) && leadingComments.length) {
|
||||
// the attached comments may include line comments, which we don't want
|
||||
leadingComments = leadingComments.filter(isBlockComment);
|
||||
|
||||
if (leadingComments.length) {
|
||||
// treat the comment closest to the node as the leading comment
|
||||
comment = getRawComment(leadingComments[leadingComments.length - 1]);
|
||||
|
||||
if ( !isValidJsdoc(comment) ) {
|
||||
comment = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
function makeVarsFinisher(scopeDoclet) {
|
||||
return ({doclet, code}) => {
|
||||
// no need to evaluate all things related to scopeDoclet again, just use it
|
||||
if ( scopeDoclet && doclet && (doclet.alias || doclet.memberof) ) {
|
||||
scopeDoclet.meta.vars[code.name] = doclet.longname;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Given an event, get the parent node's doclet.
|
||||
function getParentDocletFromEvent(parser, {doclet}) {
|
||||
if (doclet && doclet.meta && doclet.meta.code && doclet.meta.code.node &&
|
||||
doclet.meta.code.node.parent) {
|
||||
return parser._getDocletById(doclet.meta.code.node.parent.nodeId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 e => {
|
||||
let documentedParams;
|
||||
let knownParams;
|
||||
let param;
|
||||
let parentDoclet;
|
||||
|
||||
let i = 0;
|
||||
|
||||
parentDoclet = getParentDocletFromEvent(parser, e);
|
||||
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++;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of nodes that represent function parameters, find the node for the rest parameter,
|
||||
* if any.
|
||||
*
|
||||
* @private
|
||||
* @param {Array.<Object>} params - An array of nodes that represent function parameters.
|
||||
* @return {Object?} The node for the rest parameter.
|
||||
*/
|
||||
function findRestParam(params) {
|
||||
let restParam = null;
|
||||
|
||||
params.some(param => {
|
||||
if (param.type === Syntax.RestElement) {
|
||||
restParam = param;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return restParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* For functions that may include a rest parameter, create a function that will automatically update
|
||||
* the rest parameter's documentation to indicate that the parameter is repeatable. If the parameter
|
||||
* is not documented, the function's doclet will remain unchanged.
|
||||
*
|
||||
* @private
|
||||
* @return {function} A function that updates the rest parameter's documentation to indicate that
|
||||
* the parameter is repeatable.
|
||||
*/
|
||||
function makeRestParamFinisher() {
|
||||
return e => {
|
||||
const doclet = e.doclet;
|
||||
let documentedParams;
|
||||
let restNode;
|
||||
|
||||
if (!doclet) {
|
||||
return;
|
||||
}
|
||||
|
||||
documentedParams = doclet.params = doclet.params || [];
|
||||
restNode = findRestParam(e.code.node.params ||
|
||||
(e.code.node.value && e.code.node.value.params) ||
|
||||
(e.code.node.init && e.code.node.init.params) ||
|
||||
[]);
|
||||
|
||||
if (restNode) {
|
||||
for (let i = documentedParams.length - 1; i >= 0; i--) {
|
||||
if (documentedParams[i].name === restNode.argument.name) {
|
||||
documentedParams[i].variable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of nodes that represent function parameters, find the nodes for the default
|
||||
* parameters, if any.
|
||||
*
|
||||
* @private
|
||||
* @param {Array.<Object>} params - An array of nodes that represent function parameters.
|
||||
* @return {Array.<Object>} The nodes for the default parameters.
|
||||
*/
|
||||
function findDefaultParams(params) {
|
||||
const defaultParams = [];
|
||||
|
||||
params.forEach(param => {
|
||||
if (param.type === Syntax.AssignmentPattern) {
|
||||
defaultParams.push(param);
|
||||
}
|
||||
else {
|
||||
defaultParams.push(null);
|
||||
}
|
||||
});
|
||||
|
||||
return defaultParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* For functions that may have at least one parameter with default values, create a function that
|
||||
* will automatically add the parameters' default values to the function's documentation. If any
|
||||
* default value is already documented, the function's doclet will remain unchanged.
|
||||
*
|
||||
* This function is only intended to handle default parameters whose node type is `Syntax.Literal`
|
||||
* (string, numeric, and boolean literals). This is because more complex default values may include,
|
||||
* for example, references to internal variables, which it may not make sense to include in
|
||||
* documentation.
|
||||
*
|
||||
* @private
|
||||
* @return {function} A function that updates the function doclet to include the default values of
|
||||
* parameters.
|
||||
*/
|
||||
function makeDefaultParamFinisher() {
|
||||
return e => {
|
||||
let defaultValues;
|
||||
const doclet = e.doclet;
|
||||
let documentedParams;
|
||||
let paramName;
|
||||
let params;
|
||||
|
||||
if (!doclet) {
|
||||
return;
|
||||
}
|
||||
|
||||
documentedParams = doclet.params = doclet.params || [];
|
||||
params = e.code.node.params || (e.code.node.value && e.code.node.value.params) || [];
|
||||
defaultValues = findDefaultParams(params);
|
||||
|
||||
for (let i = 0, j = 0, l = params.length; i < l; i++) {
|
||||
// bail out if we ran out of documented params
|
||||
if (!documentedParams[j]) {
|
||||
break;
|
||||
}
|
||||
|
||||
// if the current parameter doesn't appear to be documented, move to the next one
|
||||
paramName = params[i].type === Syntax.AssignmentPattern ?
|
||||
params[i].left.name :
|
||||
params[i].name;
|
||||
if (paramName !== documentedParams[j].name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// add the default value iff a) a literal default value is defined in the code,
|
||||
// b) no default value is documented, and c) the default value is not an empty string
|
||||
if (defaultValues[i] &&
|
||||
defaultValues[i].right &&
|
||||
defaultValues[i].right.type === Syntax.Literal &&
|
||||
typeof documentedParams[j].defaultvalue === 'undefined' &&
|
||||
defaultValues[i].right.value !== '') {
|
||||
documentedParams[j].defaultvalue =
|
||||
jsdoc.src.astnode.nodeToValue(defaultValues[i].right);
|
||||
}
|
||||
|
||||
// move to the next documented param
|
||||
j++;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* For method definitions that are constructors, create a function that will merge portions of the
|
||||
* constructor's doclet into the class's doclet, provided that a doclet exists for the class.
|
||||
* Merging the constructor's documentation allows ES 2015 classes to be documented in a natural way,
|
||||
* with separate JSDoc comments for the class and its constructor.
|
||||
*
|
||||
* @private
|
||||
* @param {module:jsdoc/src/parser.Parser} parser - The JSDoc parser.
|
||||
* @return {function} A function that merges the constructor's doclet into the class's doclet.
|
||||
*/
|
||||
function makeConstructorFinisher(parser) {
|
||||
return e => {
|
||||
let combined;
|
||||
const doclet = e.doclet;
|
||||
let parentDoclet;
|
||||
|
||||
// for class declarations that are named module exports, the node that's documented is the
|
||||
// ExportNamedDeclaration, not the ClassDeclaration
|
||||
if (e.code.node.parent.parent.parent &&
|
||||
e.code.node.parent.parent.parent.type === Syntax.ExportNamedDeclaration) {
|
||||
parentDoclet = parser._getDocletById(e.code.node.parent.parent.parent.nodeId);
|
||||
}
|
||||
// otherwise, we want the ClassDeclaration
|
||||
else {
|
||||
parentDoclet = parser._getDocletById(e.code.node.parent.parent.nodeId);
|
||||
}
|
||||
|
||||
if (!doclet || !parentDoclet || parentDoclet.undocumented) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We prefer the parent doclet because it has the correct kind, longname, and memberof.
|
||||
// The child doclet might or might not have the correct kind, longname, and memberof.
|
||||
combined = jsdoc.doclet.combine(parentDoclet, doclet);
|
||||
parser.addResult(combined);
|
||||
|
||||
parentDoclet.undocumented = doclet.undocumented = true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a function that will add an `async` property to the doclet for async functions.
|
||||
*
|
||||
* @private
|
||||
* @return {function} A function that adds an `async` property to the doclet of async functions.
|
||||
*/
|
||||
function makeAsyncFunctionFinisher() {
|
||||
return e => {
|
||||
const doclet = e.doclet;
|
||||
|
||||
if (!doclet) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( e.code.node.async || (e.code.node.value && e.code.node.value.async) ||
|
||||
(e.code.node.init && e.code.node.init.async) ) {
|
||||
doclet.async = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a function that will mark a doclet as private.
|
||||
*
|
||||
* @private
|
||||
* @return {function} A function that marks a doclet as private.
|
||||
*/
|
||||
function makePrivatePropertyFinisher() {
|
||||
return ({doclet}) => {
|
||||
doclet.access = 'private';
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a function that will mark a doclet as a generator function.
|
||||
*
|
||||
* @private
|
||||
* @return {function} A function that marks a doclet as a generator function.
|
||||
*/
|
||||
function makeGeneratorFinisher() {
|
||||
return e => {
|
||||
const doclet = e.doclet;
|
||||
|
||||
if (!doclet) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( e.code.node.generator || (e.code.node.init && e.code.node.init.generator) ||
|
||||
(e.code.node.value && e.code.node.value.generator) ) {
|
||||
doclet.generator = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
class SymbolFound {
|
||||
// TODO: docs
|
||||
constructor(node, filename, extras = {}) {
|
||||
this.id = extras.id || node.nodeId;
|
||||
this.comment = extras.comment || getLeadingJsdocComment(node) || '@undocumented';
|
||||
this.lineno = extras.lineno || node.loc.start.line;
|
||||
this.columnno = extras.columnno || node.loc.start.column;
|
||||
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(key => {
|
||||
this[key] = extras[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
class JsdocCommentFound {
|
||||
// TODO: docs
|
||||
constructor({loc, range}, rawComment, filename) {
|
||||
this.comment = rawComment;
|
||||
this.lineno = loc.start.line;
|
||||
this.columnno = loc.start.column;
|
||||
this.filename = filename;
|
||||
this.range = range;
|
||||
|
||||
Object.defineProperty(this, 'event', {
|
||||
value: 'jsdocCommentFound'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
function hasComments(node) {
|
||||
return (node && node.leadingComments && node.leadingComments.length) ||
|
||||
(node && node.trailingComments && node.trailingComments.length) ||
|
||||
(node && node.innerComments && node.innerComments.length);
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
function removeCommentDelimiters(comment) {
|
||||
return comment.substring(2, comment.length - 2);
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
function updateCommentNode(commentNode, comment) {
|
||||
commentNode.value = removeCommentDelimiters(comment);
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
// TODO: note that it's essential to call this function before you try to resolve names!
|
||||
function trackVars(parser, {enclosingScope}, {code, finishers}) {
|
||||
let doclet;
|
||||
const enclosingScopeId = enclosingScope ? enclosingScope.nodeId : null;
|
||||
|
||||
if (enclosingScopeId) {
|
||||
doclet = parser._getDocletById(enclosingScopeId);
|
||||
}
|
||||
else {
|
||||
doclet = parser._getDocletByLongname(jsdoc.name.LONGNAMES.GLOBAL);
|
||||
}
|
||||
|
||||
if (doclet) {
|
||||
doclet.meta.vars = doclet.meta.vars || {};
|
||||
doclet.meta.vars[code.name] = null;
|
||||
finishers.push( makeVarsFinisher(doclet) );
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
function makeSymbolFoundEvent(node, parser, filename) {
|
||||
let e;
|
||||
let basename;
|
||||
let parent;
|
||||
|
||||
const 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 = jsdoc.name.getBasename(e.code.name);
|
||||
if (basename !== 'this') {
|
||||
e.code.funcscope = parser.resolveVar(node, basename);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// like `bar='baz'` in: function foo(bar='baz') {}
|
||||
case Syntax.AssignmentPattern:
|
||||
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: class foo {}
|
||||
case Syntax.ClassDeclaration:
|
||||
// falls through
|
||||
|
||||
// like: let MyClass = class {}
|
||||
case Syntax.ClassExpression:
|
||||
e = new SymbolFound(node, filename, extras);
|
||||
|
||||
trackVars(parser, node, e);
|
||||
|
||||
basename = jsdoc.name.getBasename(e.code.name);
|
||||
|
||||
break;
|
||||
|
||||
// like `#b = 1` in: class A { #b = 1; }
|
||||
case Syntax.ClassPrivateProperty:
|
||||
extras.finishers = [
|
||||
parser.resolveEnum,
|
||||
makePrivatePropertyFinisher()
|
||||
];
|
||||
|
||||
e = new SymbolFound(node, filename, extras);
|
||||
|
||||
break;
|
||||
|
||||
// like `b = 1` in: class A { b = 1; }
|
||||
case Syntax.ClassProperty:
|
||||
extras.finishers = [parser.resolveEnum];
|
||||
|
||||
e = new SymbolFound(node, filename, extras);
|
||||
|
||||
break;
|
||||
|
||||
// like: export * from 'foo'
|
||||
case Syntax.ExportAllDeclaration:
|
||||
e = new SymbolFound(node, filename, extras);
|
||||
|
||||
break;
|
||||
|
||||
// like: export default 'foo'
|
||||
case Syntax.ExportDefaultDeclaration:
|
||||
// falls through
|
||||
|
||||
// like: export var foo;
|
||||
// or: export {foo}
|
||||
case Syntax.ExportNamedDeclaration:
|
||||
// falls through
|
||||
|
||||
// like `foo as bar` in: export {foo as bar}
|
||||
case Syntax.ExportSpecifier:
|
||||
e = new SymbolFound(node, filename, extras);
|
||||
|
||||
trackVars(parser, node, e);
|
||||
|
||||
break;
|
||||
|
||||
// like: var foo = () => {};
|
||||
case Syntax.ArrowFunctionExpression:
|
||||
// falls through
|
||||
|
||||
// like: function foo() {}
|
||||
case Syntax.FunctionDeclaration:
|
||||
// falls through
|
||||
|
||||
// like: var foo = function() {};
|
||||
case Syntax.FunctionExpression:
|
||||
extras.finishers = [
|
||||
// handle cases where at least one parameter has a default value
|
||||
makeDefaultParamFinisher(),
|
||||
// handle rest parameters
|
||||
makeRestParamFinisher(),
|
||||
// handle async functions
|
||||
makeAsyncFunctionFinisher(),
|
||||
// handle generator functions
|
||||
makeGeneratorFinisher()
|
||||
];
|
||||
|
||||
e = new SymbolFound(node, filename, extras);
|
||||
|
||||
trackVars(parser, node, e);
|
||||
|
||||
basename = jsdoc.name.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 in other cases.
|
||||
case Syntax.Identifier:
|
||||
parent = node.parent;
|
||||
|
||||
// function parameters with inline comments
|
||||
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: foo() {}
|
||||
// or: constructor() {}
|
||||
case Syntax.MethodDefinition:
|
||||
extras.finishers = [
|
||||
// handle cases where at least one parameter has a default value
|
||||
makeDefaultParamFinisher(),
|
||||
// handle rest parameters
|
||||
makeRestParamFinisher(),
|
||||
// handle async functions
|
||||
makeAsyncFunctionFinisher(),
|
||||
// handle generator functions
|
||||
makeGeneratorFinisher()
|
||||
];
|
||||
// for constructors, we attempt to merge the constructor's docs into the class's docs
|
||||
if (node.kind === 'constructor') {
|
||||
extras.finishers.push( makeConstructorFinisher(parser) );
|
||||
}
|
||||
|
||||
e = new SymbolFound(node, filename, extras);
|
||||
|
||||
break;
|
||||
|
||||
// like `{}` 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' && node.kind !== 'set') {
|
||||
extras.finishers = [parser.resolveEnum];
|
||||
}
|
||||
|
||||
e = new SymbolFound(node, filename, extras);
|
||||
|
||||
break;
|
||||
|
||||
// like `...bar` in: function foo(...bar) {}
|
||||
case Syntax.RestElement:
|
||||
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: var i = 0;
|
||||
case Syntax.VariableDeclarator:
|
||||
extras.finishers = [
|
||||
// handle cases where at least one parameter has a default value
|
||||
makeDefaultParamFinisher(),
|
||||
// handle rest parameters
|
||||
makeRestParamFinisher(),
|
||||
// handle async functions
|
||||
makeAsyncFunctionFinisher(),
|
||||
// handle generator functions
|
||||
makeGeneratorFinisher()
|
||||
];
|
||||
|
||||
e = new SymbolFound(node, filename, extras);
|
||||
|
||||
trackVars(parser, node, e);
|
||||
|
||||
basename = jsdoc.name.getBasename(e.code.name);
|
||||
// auto-detect constants
|
||||
if (node.parent.kind === 'const') {
|
||||
e.code.kind = 'constant';
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (!e) {
|
||||
e = {
|
||||
finishers: []
|
||||
};
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
class Visitor {
|
||||
// TODO: docs
|
||||
constructor() {
|
||||
this._parser = null;
|
||||
|
||||
// Mozilla Parser API node visitors added by plugins
|
||||
this._nodeVisitors = [];
|
||||
// built-in visitors
|
||||
this._visitors = [
|
||||
this.visitNodeComments,
|
||||
this.visitNode
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parser instance that visitors can use.
|
||||
*
|
||||
* @param {module:jsdoc/src/parser.Parser} parser - The parser instance.
|
||||
*/
|
||||
setParser(parser) {
|
||||
this._parser = parser;
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
addAstNodeVisitor(visitor) {
|
||||
this._nodeVisitors.push(visitor);
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
removeAstNodeVisitor(visitor) {
|
||||
const idx = this._nodeVisitors.indexOf(visitor);
|
||||
|
||||
if (idx !== -1) {
|
||||
this._nodeVisitors.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
getAstNodeVisitors() {
|
||||
return this._nodeVisitors;
|
||||
}
|
||||
|
||||
// TODO: docs; visitor signature is (node, parser, filename)
|
||||
visit(node, filename) {
|
||||
for (let visitor of this._visitors) {
|
||||
visitor.call(this, node, this._parser, filename);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* eslint-disable class-methods-use-this */
|
||||
// TODO: docs
|
||||
visitNodeComments(node, parser, filename) {
|
||||
let comments;
|
||||
let e;
|
||||
const isBlock = isBlockComment(node);
|
||||
let lastTrailingComment;
|
||||
let nextProgramNode;
|
||||
let nextProgramNodeIndex;
|
||||
let rawComment;
|
||||
|
||||
function addComments(source) {
|
||||
comments = comments.concat( source.slice(0) );
|
||||
}
|
||||
|
||||
if ( !hasComments(node) && (!node.type || !isBlock) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
comments = isBlock ? [node] : [];
|
||||
|
||||
if (node.leadingComments && node.leadingComments.length) {
|
||||
addComments(node.leadingComments);
|
||||
}
|
||||
|
||||
// trailing comments are always duplicates of leading comments unless they're attached to the
|
||||
// Program node...
|
||||
if (node.type === Syntax.Program && node.trailingComments && node.trailingComments.length) {
|
||||
addComments(node.trailingComments);
|
||||
}
|
||||
|
||||
// ...or if they were comments from the end of the file that were erroneously attached to a
|
||||
// `'use strict';` declaration (https://github.com/babel/babel/issues/6688).
|
||||
if (node.type === Syntax.ExpressionStatement && node.directive === 'use strict' &&
|
||||
node.trailingComments && node.trailingComments.length) {
|
||||
// to be safe, we verify that the trailing comments came after the next node in the Program
|
||||
// body, which means the comments were attached to the wrong node
|
||||
if (node.parent.body.length > 1) {
|
||||
nextProgramNodeIndex = node.parent.body.indexOf(node) + 1;
|
||||
nextProgramNode = node.parent.body[nextProgramNodeIndex];
|
||||
lastTrailingComment = node.trailingComments[node.trailingComments.length - 1];
|
||||
|
||||
if (lastTrailingComment.start > nextProgramNode.end) {
|
||||
addComments(node.trailingComments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.innerComments && node.innerComments.length) {
|
||||
addComments(node.innerComments);
|
||||
}
|
||||
|
||||
for (let comment of comments) {
|
||||
rawComment = getRawComment(comment);
|
||||
|
||||
if ( isValidJsdoc(rawComment) ) {
|
||||
e = new JsdocCommentFound(comment, rawComment, filename);
|
||||
|
||||
parser.emit(e.event, e, parser);
|
||||
|
||||
if (e.comment !== rawComment) {
|
||||
updateCommentNode(comment, e.comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/* eslint-enable class-methods-use-this */
|
||||
|
||||
// TODO: docs
|
||||
visitNode(node, parser, filename) {
|
||||
const e = makeSymbolFoundEvent(node, parser, filename);
|
||||
|
||||
if (this._nodeVisitors && this._nodeVisitors.length) {
|
||||
for (let visitor of this._nodeVisitors) {
|
||||
visitor.visitNode(node, e, parser, filename);
|
||||
if (e.stopPropagation) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!e.preventDefault) {
|
||||
parser.emit(e.event, e, parser);
|
||||
}
|
||||
|
||||
// add the node to the parser's lookup table
|
||||
parser.addDocletRef(e);
|
||||
|
||||
for (let finisher of e.finishers) {
|
||||
finisher.call(parser, e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
exports.Visitor = Visitor;
|
||||
+704
@@ -0,0 +1,704 @@
|
||||
/**
|
||||
* Traversal utilities for ASTs that are compatible with the ESTree API.
|
||||
*
|
||||
* @module jsdoc/src/walker
|
||||
*/
|
||||
const astnode = require('jsdoc/src/astnode');
|
||||
const logger = require('jsdoc/util/logger');
|
||||
const Syntax = require('jsdoc/src/syntax').Syntax;
|
||||
|
||||
// TODO: docs
|
||||
function getCurrentScope(scopes) {
|
||||
return scopes[scopes.length - 1] || null;
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
function moveLeadingComments(source, target, count) {
|
||||
if (source.leadingComments) {
|
||||
if (count === undefined) {
|
||||
count = source.leadingComments.length;
|
||||
}
|
||||
|
||||
target.leadingComments = source.leadingComments.slice(0, count);
|
||||
source.leadingComments = source.leadingComments.slice(count);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
function moveTrailingComments(source, target, count) {
|
||||
if (source.trailingComments) {
|
||||
if (count === undefined) {
|
||||
count = source.trailingComments.length;
|
||||
}
|
||||
|
||||
target.trailingComments = source.trailingComments.slice(
|
||||
source.trailingComments.length - count, count
|
||||
);
|
||||
source.trailingComments = source.trailingComments.slice(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable no-empty-function, no-unused-vars */
|
||||
function leafNode(node, parent, state, cb) {}
|
||||
/* eslint-enable no-empty-function, no-unused-vars */
|
||||
|
||||
// TODO: docs
|
||||
const walkers = exports.walkers = {};
|
||||
|
||||
walkers[Syntax.ArrayExpression] = (node, parent, state, cb) => {
|
||||
for (let element of node.elements) {
|
||||
if (element) {
|
||||
cb(element, node, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: verify correctness
|
||||
walkers[Syntax.ArrayPattern] = (node, parent, state, cb) => {
|
||||
for (let element of node.elements) {
|
||||
// must be an identifier or an expression
|
||||
if (element && element.type !== Syntax.Identifier) {
|
||||
cb(element, node, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ArrowFunctionExpression] = (node, parent, state, cb) => {
|
||||
if (node.id) {
|
||||
cb(node.id, node, state);
|
||||
}
|
||||
|
||||
for (let param of node.params) {
|
||||
cb(param, node, state);
|
||||
}
|
||||
|
||||
cb(node.body, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.AssignmentExpression] = (node, parent, state, cb) => {
|
||||
cb(node.left, node, state);
|
||||
cb(node.right, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.AssignmentPattern] = walkers[Syntax.AssignmentExpression];
|
||||
|
||||
walkers[Syntax.AwaitExpression] = (node, parent, state, cb) => {
|
||||
cb(node.argument, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.BigIntLiteral] = leafNode;
|
||||
|
||||
walkers[Syntax.BinaryExpression] = (node, parent, state, cb) => {
|
||||
cb(node.left, node, state);
|
||||
cb(node.right, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.BindExpression] = (node, parent, state, cb) => {
|
||||
if (node.object) {
|
||||
cb(node.object, node, state);
|
||||
}
|
||||
|
||||
cb(node.callee, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.BlockStatement] = (node, parent, state, cb) => {
|
||||
for (let bodyItem of node.body) {
|
||||
cb(bodyItem, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.BreakStatement] = leafNode;
|
||||
|
||||
walkers[Syntax.CallExpression] = function(node, parent, state, cb) {
|
||||
cb(node.callee, node, state);
|
||||
|
||||
if (node.arguments) {
|
||||
for (let arg of node.arguments) {
|
||||
cb(arg, node, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.CatchClause] = leafNode;
|
||||
|
||||
walkers[Syntax.ClassBody] = walkers[Syntax.BlockStatement];
|
||||
|
||||
walkers[Syntax.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);
|
||||
}
|
||||
|
||||
if (node.decorators) {
|
||||
for (let decorator of node.decorators) {
|
||||
cb(decorator, node, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ClassExpression] = walkers[Syntax.ClassDeclaration];
|
||||
|
||||
// walkers[Syntax.ClassPrivateProperty] is defined later
|
||||
|
||||
// walkers[Syntax.ClassProperty] is defined later
|
||||
|
||||
// TODO: verify correctness
|
||||
walkers[Syntax.ComprehensionBlock] = walkers[Syntax.AssignmentExpression];
|
||||
|
||||
// TODO: verify correctness
|
||||
walkers[Syntax.ComprehensionExpression] = (node, parent, state, cb) => {
|
||||
cb(node.body, node, state);
|
||||
|
||||
if (node.filter) {
|
||||
cb(node.filter, node, state);
|
||||
}
|
||||
|
||||
for (let block of node.blocks) {
|
||||
cb(block, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.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.Decorator] = (node, parent, state, cb) => {
|
||||
cb(node.expression, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.DoExpression] = (node, parent, state, cb) => {
|
||||
cb(node.body, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.DoWhileStatement] = (node, parent, state, cb) => {
|
||||
cb(node.test, node, state);
|
||||
cb(node.body, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.EmptyStatement] = leafNode;
|
||||
|
||||
walkers[Syntax.ExperimentalRestProperty] = (node, parent, state, cb) => {
|
||||
cb(node.argument, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.ExperimentalSpreadProperty] = walkers[Syntax.ExperimentalRestProperty];
|
||||
|
||||
walkers[Syntax.ExportAllDeclaration] = (node, parent, state, cb) => {
|
||||
if (node.source) {
|
||||
cb(node.source, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ExportDefaultDeclaration] = (node, parent, state, cb) => {
|
||||
// if the declaration target is a class, move leading comments to the declaration target
|
||||
if (node.declaration && node.declaration.type === Syntax.ClassDeclaration) {
|
||||
moveLeadingComments(node, node.declaration);
|
||||
}
|
||||
|
||||
if (node.declaration) {
|
||||
cb(node.declaration, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ExportDefaultSpecifier] = (node, parent, state, cb) => {
|
||||
cb(node.exported, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.ExportNamedDeclaration] = (node, parent, state, cb) => {
|
||||
if (node.declaration) {
|
||||
cb(node.declaration, node, state);
|
||||
}
|
||||
|
||||
for (let specifier of node.specifiers) {
|
||||
cb(specifier, node, state);
|
||||
}
|
||||
|
||||
if (node.source) {
|
||||
cb(node.source, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ExportNamespaceSpecifier] = (node, parent, state, cb) => {
|
||||
cb(node.exported, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.ExportSpecifier] = (node, parent, state, cb) => {
|
||||
if (node.exported) {
|
||||
cb(node.exported, node, state);
|
||||
}
|
||||
|
||||
if (node.local) {
|
||||
cb(node.local, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ExpressionStatement] = (node, parent, state, cb) => {
|
||||
moveLeadingComments(node, node.expression);
|
||||
|
||||
cb(node.expression, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.File] = (node, parent, state, cb) => {
|
||||
cb(node.program, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.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] = (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] = (node, parent, state, cb) => {
|
||||
cb(node.test, node, state);
|
||||
cb(node.consequent, node, state);
|
||||
if (node.alternate) {
|
||||
cb(node.alternate, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.Import] = leafNode;
|
||||
|
||||
walkers[Syntax.ImportDeclaration] = (node, parent, state, cb) => {
|
||||
if (node.specifiers) {
|
||||
for (let specifier of node.specifiers) {
|
||||
cb(specifier, node, state);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.source) {
|
||||
cb(node.source, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ImportDefaultSpecifier] = (node, parent, state, cb) => {
|
||||
if (node.local) {
|
||||
cb(node.local, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ImportNamespaceSpecifier] = walkers[Syntax.ImportDefaultSpecifier];
|
||||
|
||||
walkers[Syntax.ImportSpecifier] = walkers[Syntax.ExportSpecifier];
|
||||
|
||||
walkers[Syntax.JSXAttribute] = (node, parent, state, cb) => {
|
||||
cb(node.name, node, state);
|
||||
|
||||
if (node.value) {
|
||||
cb(node.value, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.JSXClosingElement] = (node, parent, state, cb) => {
|
||||
cb(node.name, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.JSXElement] = (node, parent, state, cb) => {
|
||||
cb(node.openingElement, node, state);
|
||||
|
||||
if (node.closingElement) {
|
||||
cb(node.closingElement, node, state);
|
||||
}
|
||||
|
||||
for (let child of node.children) {
|
||||
cb(child, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.JSXEmptyExpression] = leafNode;
|
||||
|
||||
walkers[Syntax.JSXExpressionContainer] = (node, parent, state, cb) => {
|
||||
cb(node.expression, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.JSXIdentifier] = leafNode;
|
||||
|
||||
walkers[Syntax.JSXMemberExpression] = (node, parent, state, cb) => {
|
||||
cb(node.object, node, state);
|
||||
|
||||
cb(node.property, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.JSXNamespacedName] = (node, parent, state, cb) => {
|
||||
cb(node.namespace, node, state);
|
||||
|
||||
cb(node.name, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.JSXOpeningElement] = (node, parent, state, cb) => {
|
||||
cb(node.name, node, state);
|
||||
|
||||
for (let attribute of node.attributes) {
|
||||
cb(attribute, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.JSXSpreadAttribute] = (node, parent, state, cb) => {
|
||||
cb(node.argument, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.JSXText] = leafNode;
|
||||
|
||||
walkers[Syntax.LabeledStatement] = (node, parent, state, cb) => {
|
||||
cb(node.body, node, state);
|
||||
};
|
||||
|
||||
// TODO: add scope info??
|
||||
walkers[Syntax.LetStatement] = (node, parent, state, cb) => {
|
||||
for (let headItem of node.head) {
|
||||
cb(headItem.id, node, state);
|
||||
if (headItem.init) {
|
||||
cb(headItem.init, node, state);
|
||||
}
|
||||
}
|
||||
|
||||
cb(node.body, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.Literal] = leafNode;
|
||||
|
||||
walkers[Syntax.LogicalExpression] = walkers[Syntax.BinaryExpression];
|
||||
|
||||
walkers[Syntax.MemberExpression] = (node, parent, state, cb) => {
|
||||
cb(node.object, node, state);
|
||||
if (node.property) {
|
||||
cb(node.property, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.MetaProperty] = leafNode;
|
||||
|
||||
walkers[Syntax.MethodDefinition] = (node, parent, state, cb) => {
|
||||
if (node.key) {
|
||||
cb(node.key, node, state);
|
||||
}
|
||||
|
||||
if (node.value) {
|
||||
cb(node.value, node, state);
|
||||
}
|
||||
|
||||
if (node.decorators) {
|
||||
for (let decorator of node.decorators) {
|
||||
cb(decorator, node, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.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] = (node, parent, state, cb) => {
|
||||
for (let property of node.properties) {
|
||||
cb(property, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ObjectPattern] = walkers[Syntax.ObjectExpression];
|
||||
|
||||
walkers[Syntax.PrivateName] = (node, parent, state, cb) => {
|
||||
cb(node.id, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.Program] = (node, parent, state, cb) => {
|
||||
// if the first item in the body has multiple leading comments, move all but the last one to
|
||||
// this node. this happens, for example, when a file has a /** @module */ standalone comment
|
||||
// followed by one or more other comments.
|
||||
if (node.body[0] && node.body[0].leadingComments && node.body[0].leadingComments.length > 1) {
|
||||
moveLeadingComments(node.body[0], node, node.body[0].leadingComments.length - 1);
|
||||
}
|
||||
|
||||
// if the last item in the body has trailing comments, move them to this node
|
||||
if (node.body.length && node.body[node.body.length - 1].trailingComments) {
|
||||
moveTrailingComments(node.body[node.body.length - 1], node);
|
||||
}
|
||||
|
||||
for (let bodyItem of node.body) {
|
||||
cb(bodyItem, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.Property] = (node, parent, state, cb) => {
|
||||
// move leading comments from key to property node
|
||||
moveLeadingComments(node.key, node);
|
||||
|
||||
if (node.value) {
|
||||
cb(node.value, node, state);
|
||||
}
|
||||
|
||||
if (node.decorators) {
|
||||
for (let decorator of node.decorators) {
|
||||
cb(decorator, node, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ClassPrivateProperty] = (node, parent, state, cb) => {
|
||||
// move leading comments from key to property node
|
||||
moveLeadingComments(node.key, node);
|
||||
|
||||
// add `name` property to key, so we don't have to give this type of node special treatment
|
||||
// when we resolve its name
|
||||
node.key.name = node.key.id.name;
|
||||
|
||||
if (node.value) {
|
||||
cb(node.value, node, state);
|
||||
}
|
||||
|
||||
if (node.decorators) {
|
||||
for (let decorator of node.decorators) {
|
||||
cb(decorator, node, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ClassProperty] = walkers[Syntax.Property];
|
||||
|
||||
walkers[Syntax.RestElement] = (node, parent, state, cb) => {
|
||||
if (node.argument) {
|
||||
cb(node.argument, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ReturnStatement] = (node, parent, state, cb) => {
|
||||
if (node.argument) {
|
||||
cb(node.argument, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.SequenceExpression] = (node, parent, state, cb) => {
|
||||
for (let expression of node.expressions) {
|
||||
cb(expression, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.SpreadElement] = (node, parent, state, cb) => {
|
||||
if (node.argument) {
|
||||
cb(node.argument, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.Super] = leafNode;
|
||||
|
||||
walkers[Syntax.SwitchCase] = (node, parent, state, cb) => {
|
||||
if (node.test) {
|
||||
cb(node.test, node, state);
|
||||
}
|
||||
|
||||
for (let consequentItem of node.consequent) {
|
||||
cb(consequentItem, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.SwitchStatement] = (node, parent, state, cb) => {
|
||||
cb(node.discriminant, node, state);
|
||||
|
||||
for (let caseItem of node.cases) {
|
||||
cb(caseItem, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.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] = (node, parent, state, cb) => {
|
||||
if (node.quasis && node.quasis.length) {
|
||||
for (let quasi of node.quasis) {
|
||||
cb(quasi, node, state);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.expressions && node.expressions.length) {
|
||||
for (let expression of node.expressions) {
|
||||
cb(expression, node, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.ThisExpression] = leafNode;
|
||||
|
||||
walkers[Syntax.ThrowStatement] = (node, parent, state, cb) => {
|
||||
cb(node.argument, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.TryStatement] = (node, parent, state, cb) => {
|
||||
cb(node.block, node, state);
|
||||
|
||||
if (node.handler) {
|
||||
cb(node.handler.body, node, state);
|
||||
}
|
||||
|
||||
if (node.finalizer) {
|
||||
cb(node.finalizer, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.UnaryExpression] = (node, parent, state, cb) => {
|
||||
cb(node.argument, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.UpdateExpression] = walkers[Syntax.UnaryExpression];
|
||||
|
||||
walkers[Syntax.VariableDeclaration] = (node, parent, state, cb) => {
|
||||
// move leading comments to first declarator
|
||||
moveLeadingComments(node, node.declarations[0]);
|
||||
|
||||
for (let declaration of node.declarations) {
|
||||
cb(declaration, node, state);
|
||||
}
|
||||
};
|
||||
|
||||
walkers[Syntax.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] = (node, parent, state, cb) => {
|
||||
cb(node.object, node, state);
|
||||
cb(node.body, node, state);
|
||||
};
|
||||
|
||||
walkers[Syntax.YieldExpression] = (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.
|
||||
*
|
||||
* @memberof module:jsdoc/src/walker
|
||||
*/
|
||||
class Walker {
|
||||
// TODO: docs
|
||||
constructor(walkerFuncs = walkers) {
|
||||
this._walkers = walkerFuncs;
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
_recurse(filename, ast) {
|
||||
const self = this;
|
||||
const state = {
|
||||
filename: filename,
|
||||
nodes: [],
|
||||
scopes: []
|
||||
};
|
||||
|
||||
function logUnknownNodeType({type}) {
|
||||
logger.debug('Found a node with unrecognized type %s. Ignoring the node and its ' +
|
||||
'descendants.', type);
|
||||
}
|
||||
|
||||
function cb(node, parent, cbState) {
|
||||
let currentScope;
|
||||
|
||||
const isScope = astnode.isScope(node);
|
||||
|
||||
astnode.addNodeProperties(node);
|
||||
node.parent = parent || null;
|
||||
|
||||
currentScope = getCurrentScope(cbState.scopes);
|
||||
if (currentScope) {
|
||||
node.enclosingScope = currentScope;
|
||||
}
|
||||
|
||||
if (isScope) {
|
||||
cbState.scopes.push(node);
|
||||
}
|
||||
cbState.nodes.push(node);
|
||||
|
||||
if (!self._walkers[node.type]) {
|
||||
logUnknownNodeType(node);
|
||||
} else {
|
||||
self._walkers[node.type](node, parent, cbState, cb);
|
||||
}
|
||||
|
||||
if (isScope) {
|
||||
cbState.scopes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
cb(ast, null, state);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
// TODO: docs
|
||||
recurse(ast, visitor, filename) {
|
||||
let shouldContinue;
|
||||
const state = this._recurse(filename, ast);
|
||||
|
||||
if (visitor) {
|
||||
for (let node of state.nodes) {
|
||||
shouldContinue = visitor.visit(node, filename);
|
||||
if (!shouldContinue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ast;
|
||||
}
|
||||
}
|
||||
exports.Walker = Walker;
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Functionality related to JSDoc tags.
|
||||
* @module jsdoc/tag
|
||||
* @requires module:jsdoc/env
|
||||
* @requires module:jsdoc/path
|
||||
* @requires module:jsdoc/tag/dictionary
|
||||
* @requires module:jsdoc/tag/validator
|
||||
* @requires module:jsdoc/tag/type
|
||||
* @requires module:jsdoc/util/logger
|
||||
* @requires module:util
|
||||
*/
|
||||
const jsdoc = {
|
||||
env: require('jsdoc/env'),
|
||||
tag: {
|
||||
dictionary: require('jsdoc/tag/dictionary'),
|
||||
validator: require('jsdoc/tag/validator'),
|
||||
type: require('jsdoc/tag/type')
|
||||
},
|
||||
util: {
|
||||
logger: require('jsdoc/util/logger')
|
||||
}
|
||||
};
|
||||
const path = require('jsdoc/path');
|
||||
const 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) {
|
||||
let indentMatcher;
|
||||
let match;
|
||||
|
||||
opts = opts || {};
|
||||
text = String(typeof text === 'undefined' ? '' : 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: Boolean(jsdoc.env.opts.debug),
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
function parseType({text, originalTitle}, {canHaveName, canHaveType}, meta) {
|
||||
try {
|
||||
return jsdoc.tag.type.parse(text, canHaveName, 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)}${meta.lineno ? (` in line ${meta.lineno}`) : ''}` ) : '',
|
||||
originalTitle,
|
||||
text,
|
||||
e.message
|
||||
);
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function processTagText(tag, tagDef, meta) {
|
||||
let 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(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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a single doclet tag.
|
||||
*/
|
||||
class Tag {
|
||||
/**
|
||||
* Constructs a new tag object. Calls the tag validator.
|
||||
*
|
||||
* @param {string} tagTitle
|
||||
* @param {string=} tagBody
|
||||
* @param {object=} meta
|
||||
*/
|
||||
constructor(tagTitle, tagBody, meta) {
|
||||
let tagDef;
|
||||
let 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);
|
||||
}
|
||||
}
|
||||
exports.Tag = Tag;
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/** @module jsdoc/tag/dictionary */
|
||||
const definitions = require('jsdoc/tag/dictionary/definitions');
|
||||
|
||||
const hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
|
||||
let dictionary;
|
||||
|
||||
/** @private */
|
||||
class TagDefinition {
|
||||
constructor(dict, title, etc) {
|
||||
const self = this;
|
||||
|
||||
etc = etc || {};
|
||||
|
||||
this.title = dict.normalise(title);
|
||||
|
||||
Object.defineProperty(this, '_dictionary', {
|
||||
value: dict
|
||||
});
|
||||
|
||||
Object.keys(etc).forEach(p => {
|
||||
self[p] = etc[p];
|
||||
});
|
||||
}
|
||||
|
||||
/** @private */
|
||||
synonym(synonymName) {
|
||||
this._dictionary.defineSynonym(this.title, synonymName);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @alias module:jsdoc/tag/dictionary.Dictionary
|
||||
*/
|
||||
class Dictionary {
|
||||
constructor() {
|
||||
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'];
|
||||
}
|
||||
|
||||
_defineNamespace(title) {
|
||||
title = this.normalise(title || '');
|
||||
|
||||
if (title && !this._namespaces.includes(title)) {
|
||||
this._namespaces.push(title);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
defineTag(title, opts) {
|
||||
const tagDef = new TagDefinition(this, title, opts);
|
||||
|
||||
this._tags[tagDef.title] = tagDef;
|
||||
|
||||
if (opts && opts.isNamespace) {
|
||||
this._defineNamespace(tagDef.title);
|
||||
}
|
||||
|
||||
return this._tags[tagDef.title];
|
||||
}
|
||||
|
||||
defineSynonym(title, synonym) {
|
||||
this._tagSynonyms[synonym.toLowerCase()] = this.normalise(title);
|
||||
}
|
||||
|
||||
getNamespaces() {
|
||||
return this._namespaces.slice(0);
|
||||
}
|
||||
|
||||
lookUp(title) {
|
||||
title = this.normalise(title);
|
||||
|
||||
if ( hasOwnProp.call(this._tags, title) ) {
|
||||
return this._tags[title];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
isNamespace(kind) {
|
||||
if (kind) {
|
||||
kind = this.normalise(kind);
|
||||
if (this._namespaces.includes(kind)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
normalise(title) {
|
||||
const canonicalName = title.toLowerCase();
|
||||
|
||||
if ( hasOwnProp.call(this._tagSynonyms, canonicalName) ) {
|
||||
return this._tagSynonyms[canonicalName];
|
||||
}
|
||||
|
||||
return canonicalName;
|
||||
}
|
||||
|
||||
normalize(title) {
|
||||
return this.normalise(title);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
+1089
File diff suppressed because it is too large
Load Diff
+135
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @module jsdoc/tag/inline
|
||||
*/
|
||||
/**
|
||||
* 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 = '\\S+', prefix = '', suffix = '') {
|
||||
return new RegExp(`${prefix}\\{@${tagName}\\s+((?:.|\n)+?)\\}${suffix}`, 'i');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = (string, tagName) => 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 = (string, replacers) => {
|
||||
const tagInfo = [];
|
||||
|
||||
function replaceMatch(replacer, tag, match, text) {
|
||||
const matchedTag = {
|
||||
completeTag: match,
|
||||
tag: tag,
|
||||
text: text
|
||||
};
|
||||
|
||||
tagInfo.push(matchedTag);
|
||||
|
||||
return replacer(string, matchedTag);
|
||||
}
|
||||
|
||||
string = string || '';
|
||||
|
||||
Object.keys(replacers).forEach(replacer => {
|
||||
const tagRegExp = regExpFactory(replacer);
|
||||
let matches;
|
||||
let previousString;
|
||||
|
||||
// call the replacer once for each match
|
||||
do {
|
||||
matches = tagRegExp.exec(string);
|
||||
if (matches) {
|
||||
previousString = string;
|
||||
string = replaceMatch(replacers[replacer], replacer, matches[0], matches[1]);
|
||||
}
|
||||
} while (matches && previousString !== string);
|
||||
});
|
||||
|
||||
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 = (string, tag, replacer) => {
|
||||
const 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 = (string, tag) => exports.replaceInlineTag(string, tag, (str, {completeTag}) => str.replace(completeTag, ''));
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* @module jsdoc/tag/type
|
||||
*/
|
||||
const catharsis = require('catharsis');
|
||||
const jsdoc = {
|
||||
name: require('jsdoc/name'),
|
||||
tag: {
|
||||
inline: require('jsdoc/tag/inline')
|
||||
},
|
||||
util: {
|
||||
cast: require('jsdoc/util/cast')
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
let completeExpression;
|
||||
let count = 0;
|
||||
let position = 0;
|
||||
let expression = '';
|
||||
const startIndex = string.search(/\{[^@]/);
|
||||
let 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) {
|
||||
let name = '';
|
||||
let typeExpression = '';
|
||||
let text = tagValue;
|
||||
let expressionAndText;
|
||||
let nameAndDescription;
|
||||
let 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 = jsdoc.util.cast.cast(RegExp.$2);
|
||||
}
|
||||
}
|
||||
|
||||
return tagInfo;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
function getTypeStrings(parsedType, isOutermostType) {
|
||||
let applications;
|
||||
let typeString;
|
||||
|
||||
let types = [];
|
||||
|
||||
const 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(application =>
|
||||
catharsis.stringify(application)).join(', ');
|
||||
typeString = `${getTypeStrings(parsedType.expression)[0]}.<${applications}>`;
|
||||
|
||||
types.push(typeString);
|
||||
}
|
||||
else {
|
||||
types.push( catharsis.stringify(parsedType) );
|
||||
}
|
||||
break;
|
||||
case TYPES.TypeUnion:
|
||||
parsedType.elements.forEach(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(`unrecognized type ${parsedType.type} in parsed 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) {
|
||||
let parsedType;
|
||||
|
||||
// don't try to parse empty type expressions
|
||||
if (!tagInfo.typeExpression) {
|
||||
return tagInfo;
|
||||
}
|
||||
|
||||
try {
|
||||
parsedType = catharsis.parse(tagInfo.typeExpression, {
|
||||
jsdoc: true,
|
||||
useCache: false
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
// always re-throw so the caller has a chance to report which file was bad
|
||||
throw new Error(`Invalid type expression "${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(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)
|
||||
const 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 = (tagValue, canHaveName, canHaveType) => {
|
||||
let tagInfo;
|
||||
|
||||
if (typeof tagValue !== 'string') {
|
||||
tagValue = '';
|
||||
}
|
||||
|
||||
tagInfo = getTagInfo(tagValue, canHaveName, canHaveType);
|
||||
tagInfo.type = tagInfo.type || [];
|
||||
|
||||
typeParsers.forEach(parser => {
|
||||
tagInfo = parser(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;
|
||||
};
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @module jsdoc/tag/validator
|
||||
* @requires jsdoc/tag/dictionary
|
||||
*/
|
||||
const env = require('jsdoc/env');
|
||||
const logger = require('jsdoc/util/logger');
|
||||
|
||||
function buildMessage(tagName, {filename, lineno, comment}, desc) {
|
||||
let result = `The @${tagName} tag ${desc}. File: ${filename}, line: ${lineno}`;
|
||||
|
||||
if (comment) {
|
||||
result += `\n${comment}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given tag.
|
||||
*/
|
||||
exports.validate = ({title, text, value}, tagDef, meta) => {
|
||||
const allowUnknownTags = env.conf.tags.allowUnknownTags;
|
||||
|
||||
// handle cases where the tag definition does not exist
|
||||
if (!tagDef) {
|
||||
// log an error if unknown tags are not allowed
|
||||
if (!allowUnknownTags ||
|
||||
(Array.isArray(allowUnknownTags) &&
|
||||
!allowUnknownTags.includes(title))) {
|
||||
logger.error( buildMessage(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 (!text && tagDef.mustHaveValue) {
|
||||
logger.error( buildMessage(title, meta, 'requires a value') );
|
||||
}
|
||||
|
||||
// check for minor issues that are usually harmless
|
||||
else if (text && tagDef.mustNotHaveValue) {
|
||||
logger.warn( buildMessage(title, meta,
|
||||
'does not permit a value; the value will be ignored') );
|
||||
}
|
||||
else if (value && value.description && tagDef.mustNotHaveDescription) {
|
||||
logger.warn( buildMessage(title, meta,
|
||||
'does not permit a description; the description will be ignored') );
|
||||
}
|
||||
};
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Wrapper for underscore's template utility to allow loading templates from files.
|
||||
* @module jsdoc/template
|
||||
*/
|
||||
const _ = require('underscore');
|
||||
const fs = require('jsdoc/fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Underscore template helper.
|
||||
*/
|
||||
class Template {
|
||||
/**
|
||||
* @param {string} filepath - Templates directory.
|
||||
*/
|
||||
constructor(filepath) {
|
||||
this.path = filepath;
|
||||
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.
|
||||
*/
|
||||
load(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.
|
||||
*/
|
||||
partial(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.
|
||||
*/
|
||||
render(file, data) {
|
||||
// main content
|
||||
let content = this.partial(file, data);
|
||||
|
||||
// apply layout
|
||||
if (this.layout) {
|
||||
data.content = content;
|
||||
content = this.partial(this.layout, data);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
exports.Template = Template;
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* @module jsdoc/tutorial
|
||||
*/
|
||||
const markdown = require('jsdoc/util/markdown');
|
||||
|
||||
const 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({children}, child) {
|
||||
const index = children.indexOf(child);
|
||||
|
||||
if (index !== -1) {
|
||||
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({children}, child) {
|
||||
children.push(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single JSDoc tutorial.
|
||||
*/
|
||||
class Tutorial {
|
||||
/**
|
||||
* @param {string} name - Tutorial name.
|
||||
* @param {string} content - Text content.
|
||||
* @param {number} type - Source formating.
|
||||
|
||||
*/
|
||||
constructor(name, content, type) {
|
||||
this.title = this.name = this.longname = 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.
|
||||
*/
|
||||
setParent(parent) {
|
||||
// removes node from old parent
|
||||
if (this.parent) {
|
||||
removeChild(this.parent, this);
|
||||
}
|
||||
|
||||
this.parent = parent;
|
||||
if (parent) {
|
||||
addChild(parent, this);
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable class-methods-use-this */
|
||||
/**
|
||||
* Removes children from current node.
|
||||
*
|
||||
* @param {Tutorial} child - Old child.
|
||||
*/
|
||||
removeChild(child) {
|
||||
child.setParent(null);
|
||||
}
|
||||
/* eslint-enable class-methods-use-this */
|
||||
|
||||
/**
|
||||
* Adds new children to current node.
|
||||
*
|
||||
* @param {Tutorial} child - New child.
|
||||
*/
|
||||
addChild(child) {
|
||||
child.setParent(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares source.
|
||||
*
|
||||
* @return {string} HTML source.
|
||||
*/
|
||||
parse() {
|
||||
switch (this.type) {
|
||||
// nothing to do
|
||||
case exports.TYPES.HTML:
|
||||
return this.content;
|
||||
|
||||
// markdown
|
||||
case exports.TYPES.MARKDOWN:
|
||||
return markdown.getParser()(this.content);
|
||||
|
||||
// uhm... should we react somehow?
|
||||
// if not then this case can be merged with TYPES.HTML
|
||||
default:
|
||||
return this.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.Tutorial = Tutorial;
|
||||
|
||||
/**
|
||||
* Represents the root tutorial.
|
||||
* @extends {module:jsdoc/tutorial.Tutorial}
|
||||
*/
|
||||
class RootTutorial extends Tutorial {
|
||||
constructor() {
|
||||
super('', '', null);
|
||||
|
||||
this._tutorials = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a tutorial by name.
|
||||
* @param {string} name - Tutorial name.
|
||||
* @return {module:jsdoc/tutorial.Tutorial} Tutorial instance.
|
||||
*/
|
||||
getByName(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.
|
||||
*/
|
||||
_addTutorial(child) {
|
||||
this._tutorials[child.name] = child;
|
||||
}
|
||||
}
|
||||
exports.RootTutorial = RootTutorial;
|
||||
|
||||
/**
|
||||
* Tutorial source types.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
exports.TYPES = {
|
||||
HTML: 1,
|
||||
MARKDOWN: 2
|
||||
};
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* @module jsdoc/tutorial/resolver
|
||||
*/
|
||||
const env = require('jsdoc/env');
|
||||
const fs = require('jsdoc/fs');
|
||||
const logger = require('jsdoc/util/logger');
|
||||
const path = require('path');
|
||||
const stripBom = require('jsdoc/util/stripbom');
|
||||
const tutorial = require('jsdoc/tutorial');
|
||||
|
||||
const hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
|
||||
// TODO: make this an instance member of `RootTutorial`?
|
||||
const conf = {};
|
||||
const 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) {
|
||||
let 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 (let childName of names) {
|
||||
addTutorialConf(childName, meta.children[childName]);
|
||||
}
|
||||
// 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 ${name} is defined more than once. Only the first definition will be used.`);
|
||||
} else {
|
||||
conf[name] = meta;
|
||||
}
|
||||
} else {
|
||||
// keys are tutorial names, values are `Tutorial` instances
|
||||
names = Object.keys(meta);
|
||||
for (let tutorialName of names) {
|
||||
addTutorialConf(tutorialName, meta[tutorialName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tutorial.
|
||||
* @param {module:jsdoc/tutorial.Tutorial} current - Tutorial to add.
|
||||
*/
|
||||
exports.addTutorial = 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 = filepath => {
|
||||
let content;
|
||||
let current;
|
||||
const files = fs.ls(filepath, env.opts.recurse ? env.conf.recurseDepth : undefined);
|
||||
let name;
|
||||
let match;
|
||||
let type;
|
||||
|
||||
// tutorials handling
|
||||
files.forEach(file => {
|
||||
match = file.match(finder);
|
||||
|
||||
// any filetype that can apply to tutorials
|
||||
if (match) {
|
||||
name = path.basename(match[1]);
|
||||
content = fs.readFileSync(file, 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':
|
||||
addTutorialConf(name, JSON.parse(stripBom.strip(content)));
|
||||
|
||||
// don't add this as a tutorial
|
||||
return;
|
||||
|
||||
// how can it be? check `finder' regexp
|
||||
// not a file we want to work with
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
current = new tutorial.Tutorial(name, content, type);
|
||||
exports.addTutorial(current);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves hierarchical structure.
|
||||
*/
|
||||
exports.resolve = () => {
|
||||
let item;
|
||||
let current;
|
||||
|
||||
Object.keys(conf).forEach(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(child => {
|
||||
const childTutorial = exports.root.getByName(child);
|
||||
|
||||
if (!childTutorial) {
|
||||
logger.error('Missing child tutorial: %s', child);
|
||||
}
|
||||
else {
|
||||
childTutorial.setParent(current);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Module to convert values between various JavaScript types.
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check whether a string contains a boolean or numeric value, and convert the string to the
|
||||
* appropriate type if necessary.
|
||||
*
|
||||
* @private
|
||||
* @param {string} str - The string to convert.
|
||||
* @return {(string|number|boolean)} The converted value.
|
||||
*/
|
||||
function castString(str) {
|
||||
let number;
|
||||
let result;
|
||||
|
||||
switch (str) {
|
||||
case 'true':
|
||||
result = true;
|
||||
break;
|
||||
|
||||
case 'false':
|
||||
result = false;
|
||||
break;
|
||||
|
||||
case 'NaN':
|
||||
result = NaN;
|
||||
break;
|
||||
|
||||
case 'null':
|
||||
result = null;
|
||||
break;
|
||||
|
||||
case 'undefined':
|
||||
result = undefined;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (typeof str === 'string') {
|
||||
if (str.includes('.')) {
|
||||
number = parseFloat(str);
|
||||
}
|
||||
else {
|
||||
number = parseInt(str, 10);
|
||||
}
|
||||
|
||||
if ( String(number) === str && !isNaN(number) ) {
|
||||
result = number;
|
||||
}
|
||||
else {
|
||||
result = str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a string contains a boolean or numeric value, and convert the string to the
|
||||
* appropriate type if necessary.
|
||||
*
|
||||
* If an object or array is passed to this method, the object or array's values will be recursively
|
||||
* converted to the appropriate types. The original object or array is not modified.
|
||||
*
|
||||
* @private
|
||||
* @param {(string|Object|Array)} item - The item whose type will be converted.
|
||||
* @return {(string|number|boolean|Object|Array)} The converted value.
|
||||
*/
|
||||
exports.cast = function cast(item) {
|
||||
let result;
|
||||
|
||||
if ( Array.isArray(item) ) {
|
||||
result = [];
|
||||
for (let i = 0, l = item.length; i < l; i++) {
|
||||
result[i] = cast(item[i]);
|
||||
}
|
||||
}
|
||||
else if (typeof item === 'object' && item !== null) {
|
||||
result = {};
|
||||
Object.keys(item).forEach(prop => {
|
||||
result[prop] = cast(item[prop]);
|
||||
});
|
||||
}
|
||||
else if (typeof item === 'string') {
|
||||
result = castString(item);
|
||||
}
|
||||
else {
|
||||
result = item;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Deep clone a simple object. Ignores non-enumerable properties.
|
||||
* @private
|
||||
*/
|
||||
const setDefined = typeof Set !== 'undefined';
|
||||
|
||||
function addItem(set, item) {
|
||||
if (setDefined) {
|
||||
set.add(item);
|
||||
}
|
||||
else if (!set.includes(item)) {
|
||||
set.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
function hasItem(set, item) {
|
||||
if (setDefined) {
|
||||
return set.has(item);
|
||||
}
|
||||
else {
|
||||
return set.includes(item);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: can we remove the circular-ref checking? pretty sure it's not needed anymore...
|
||||
// if we still need this for some reason, we should share code with jsdoc/util/dumper
|
||||
function doop(o, seen) {
|
||||
let clone;
|
||||
let descriptor;
|
||||
let props;
|
||||
let i;
|
||||
let 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;
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Recursively print out all names and values in a data structure.
|
||||
* @module jsdoc/util/dumper
|
||||
*/
|
||||
const util = require('util');
|
||||
|
||||
const OBJECT_WALKER_KEY = 'hasBeenSeenByWalkerDumper';
|
||||
const SET_DEFINED = (typeof Set !== 'undefined');
|
||||
|
||||
class ObjectWalker {
|
||||
constructor() {
|
||||
this.seenItems = SET_DEFINED ? new Set() : [];
|
||||
}
|
||||
|
||||
seen(object) {
|
||||
let result;
|
||||
|
||||
if (SET_DEFINED) {
|
||||
result = this.seenItems.has(object);
|
||||
}
|
||||
else {
|
||||
result = object[OBJECT_WALKER_KEY];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
markAsSeen(object) {
|
||||
if (SET_DEFINED) {
|
||||
this.seenItems.add(object);
|
||||
}
|
||||
else {
|
||||
object[OBJECT_WALKER_KEY] = true;
|
||||
this.seenItems.push(object);
|
||||
}
|
||||
}
|
||||
|
||||
removeSeenFlag(obj) {
|
||||
if (SET_DEFINED) {
|
||||
this.seenItems.delete(obj);
|
||||
}
|
||||
else {
|
||||
delete obj[OBJECT_WALKER_KEY];
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable class-methods-use-this */
|
||||
// some objects are unwalkable, like Java native objects
|
||||
isUnwalkable(o) {
|
||||
return o && typeof o === 'object' && typeof o.constructor === 'undefined';
|
||||
}
|
||||
|
||||
isFunction(o) {
|
||||
return (o && typeof o === 'function') || o instanceof Function;
|
||||
}
|
||||
|
||||
isObject(o) {
|
||||
return (o && o instanceof Object) ||
|
||||
(o && typeof o.constructor !== 'undefined' && o.constructor.name === 'Object');
|
||||
}
|
||||
/* eslint-enable class-methods-use-this */
|
||||
|
||||
checkCircularRefs(o, func) {
|
||||
if ( this.seen(o) ) {
|
||||
return '<CircularRef>';
|
||||
}
|
||||
else {
|
||||
this.markAsSeen(o);
|
||||
|
||||
return func(o);
|
||||
}
|
||||
}
|
||||
|
||||
walk(o) {
|
||||
let result;
|
||||
|
||||
const self = this;
|
||||
|
||||
if ( this.isUnwalkable(o) ) {
|
||||
result = '<Object>';
|
||||
}
|
||||
else if ( o === undefined ) {
|
||||
result = null;
|
||||
}
|
||||
else if ( Array.isArray(o) ) {
|
||||
result = this.checkCircularRefs(o, arr => {
|
||||
const newArray = [];
|
||||
|
||||
arr.forEach(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, obj => {
|
||||
const newObj = {};
|
||||
|
||||
Object.keys(obj).forEach(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(...args) {
|
||||
const result = [];
|
||||
let walker;
|
||||
|
||||
for (let arg of args) {
|
||||
walker = new ObjectWalker();
|
||||
result.push( JSON.stringify(walker.walk(arg), null, 4) );
|
||||
}
|
||||
|
||||
return result.join('\n');
|
||||
};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
/**
|
||||
* 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 = e => {
|
||||
const logger = require('jsdoc/util/logger');
|
||||
let 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);
|
||||
};
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const util = require('util');
|
||||
|
||||
/* eslint-disable no-empty-function */
|
||||
class Logger extends EventEmitter {}
|
||||
/* eslint-enable no-empty-function */
|
||||
|
||||
const logger = module.exports = new Logger();
|
||||
|
||||
/**
|
||||
* Logging levels for the JSDoc logger. The default logging level is
|
||||
* {@link module:jsdoc/util/logger.LEVELS.ERROR}.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.LEVELS
|
||||
* @enum
|
||||
* @type {number}
|
||||
*/
|
||||
const LEVELS = logger.LEVELS = {
|
||||
/**
|
||||
* Do not log any messages.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.LEVELS.SILENT
|
||||
*/
|
||||
SILENT: 0,
|
||||
/**
|
||||
* Log fatal errors that prevent JSDoc from running.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.LEVELS.FATAL
|
||||
*/
|
||||
FATAL: 10,
|
||||
/**
|
||||
* Log all errors, including errors from which JSDoc can recover.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.LEVELS.ERROR
|
||||
*/
|
||||
ERROR: 20,
|
||||
/**
|
||||
* Log the following messages:
|
||||
*
|
||||
* + Warnings
|
||||
* + Errors
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.LEVELS.WARN
|
||||
*/
|
||||
WARN: 30,
|
||||
/**
|
||||
* Log the following messages:
|
||||
*
|
||||
* + Informational messages
|
||||
* + Warnings
|
||||
* + Errors
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.LEVELS.INFO
|
||||
*/
|
||||
INFO: 40,
|
||||
/**
|
||||
* Log the following messages:
|
||||
*
|
||||
* + Debugging messages
|
||||
* + Informational messages
|
||||
* + Warnings
|
||||
* + Errors
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.LEVELS.DEBUG
|
||||
*/
|
||||
DEBUG: 50,
|
||||
/**
|
||||
* Log all messages.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.LEVELS.VERBOSE
|
||||
*/
|
||||
VERBOSE: 1000
|
||||
};
|
||||
|
||||
const DEFAULT_LEVEL = LEVELS.WARN;
|
||||
let logLevel = DEFAULT_LEVEL;
|
||||
|
||||
const PREFIXES = {
|
||||
DEBUG: 'DEBUG: ',
|
||||
ERROR: 'ERROR: ',
|
||||
FATAL: 'FATAL: ',
|
||||
WARN: 'WARNING: '
|
||||
};
|
||||
|
||||
// Add a prefix to a log message if necessary.
|
||||
function addPrefix(args, prefix) {
|
||||
let 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) {
|
||||
const eventName = `logger:${name}`;
|
||||
const upperCaseName = name.toUpperCase();
|
||||
const level = LEVELS[upperCaseName];
|
||||
const prefix = PREFIXES[upperCaseName];
|
||||
|
||||
return function() {
|
||||
let loggerArgs;
|
||||
|
||||
const args = Array.prototype.slice.call(arguments, 0);
|
||||
|
||||
if (logLevel >= level) {
|
||||
loggerArgs = addPrefix(args, prefix);
|
||||
func(...loggerArgs);
|
||||
}
|
||||
|
||||
args.unshift(eventName);
|
||||
logger.emit(...args);
|
||||
};
|
||||
}
|
||||
|
||||
// Print a message to STDOUT without a terminating newline.
|
||||
function printToStdout(...args) {
|
||||
process.stdout.write( util.format(...args) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.DEBUG}.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.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.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.printDebug
|
||||
* @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}.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.error
|
||||
* @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}.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.fatal
|
||||
* @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}.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.info
|
||||
* @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.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.printInfo
|
||||
* @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}.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.verbose
|
||||
* @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.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.printVerbose
|
||||
* @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}.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.warn
|
||||
* @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.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.setLevel
|
||||
* @param {module:jsdoc/util/logger.LEVELS} level - The log level to use.
|
||||
*/
|
||||
logger.setLevel = function(level) {
|
||||
logLevel = (level !== undefined) ? level : DEFAULT_LEVEL;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current log level.
|
||||
*
|
||||
* @alias module:jsdoc/util/logger.getLevel
|
||||
* @return {module:jsdoc/util/logger.LEVELS} The current log level.
|
||||
*/
|
||||
logger.getLevel = function() {
|
||||
return logLevel;
|
||||
};
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* Provides access to Markdown-related functions.
|
||||
* @module jsdoc/util/markdown
|
||||
*/
|
||||
const env = require('jsdoc/env');
|
||||
const logger = require('jsdoc/util/logger');
|
||||
const MarkdownIt = require('markdown-it');
|
||||
const { marked } = require('marked');
|
||||
const mda = require('markdown-it-anchor');
|
||||
const path = require('jsdoc/path');
|
||||
const util = require('util');
|
||||
|
||||
/**
|
||||
* Enumeration of Markdown parsers that are available.
|
||||
* @enum {String}
|
||||
*/
|
||||
const parserNames = {
|
||||
/**
|
||||
* The [`markdown-js`](https://github.com/evilstreak/markdown-js) (aka "evilstreak") parser.
|
||||
*
|
||||
* @deprecated Replaced by `markdown-it`.
|
||||
*/
|
||||
evilstreak: 'markdownit',
|
||||
/**
|
||||
* The "GitHub-flavored Markdown" parser.
|
||||
*
|
||||
* @deprecated Replaced by `markdown-it`.
|
||||
*/
|
||||
gfm: 'markdownit',
|
||||
/**
|
||||
* The `markdown-it` parser.
|
||||
*/
|
||||
markdownit: 'markdownit',
|
||||
/**
|
||||
* The [Marked](https://github.com/chjj/marked) parser.
|
||||
*
|
||||
* @deprecated Will be replaced by `markdown-it` in JSDoc 3.7.0.
|
||||
*/
|
||||
marked: 'marked'
|
||||
};
|
||||
|
||||
/**
|
||||
* Escape underscores that occur within an inline tag in order to protect them from the `marked`
|
||||
* parser.
|
||||
*
|
||||
* @param {string} source - The source text to sanitize.
|
||||
* @return {string} The source text, where underscores within inline tags have been protected with a
|
||||
* preceding backslash (e.g., `\_`). The `marked` parser will strip the backslash and protect the
|
||||
* underscore.
|
||||
*/
|
||||
function escapeUnderscores(source) {
|
||||
return source.replace(/\{@[^}\r\n]+\}/g, wholeMatch => 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 backslashes within inline tags so that they are not stripped.
|
||||
*
|
||||
* @param {string} source - The source text to escape.
|
||||
* @return {string} The source text with backslashes escaped within inline tags.
|
||||
*/
|
||||
function escapeInlineTagBackslashes(source) {
|
||||
return source.replace(/\{@[^}\r\n]+\}/g, wholeMatch => wholeMatch.replace(/\\/g, '\\\\'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, ''');
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a code snippet in HTML tags that enable syntax highlighting.
|
||||
*
|
||||
* @param {string} code - The code snippet.
|
||||
* @param {string?} language - The language of the code snippet.
|
||||
* @return {string} The wrapped code snippet.
|
||||
*/
|
||||
function highlight(code, language) {
|
||||
let classString;
|
||||
let langClass = '';
|
||||
|
||||
if (language && (language !== 'plain')) {
|
||||
langClass = ` lang-${language}`;
|
||||
}
|
||||
|
||||
if (language !== 'plain') {
|
||||
classString = util.format(' class="prettyprint source%s"', langClass);
|
||||
}
|
||||
else {
|
||||
classString = ' class="source"';
|
||||
}
|
||||
|
||||
return util.format('<pre%s><code>%s</code></pre>', classString, escapeCode(code));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unencode quotes that occur within {@ ... } after the Markdown parser has turned them into HTML
|
||||
* entities.
|
||||
*
|
||||
* @param {string} source - The source text to unencode.
|
||||
* @return {string} The source text with HTML entity `"` converted back to standard quotes.
|
||||
*/
|
||||
function unencodeQuotes(source) {
|
||||
return source.replace(/\{@[^}\r\n]+\}/g, wholeMatch => wholeMatch.replace(/"/g, '"'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate function for applying syntax highlighting to text, based on the user's
|
||||
* Markdown configuration settings.
|
||||
*
|
||||
* @param {Object} conf - The user's Markdown configuration settings.
|
||||
* @return {function} The highlighter function.
|
||||
*/
|
||||
function getHighlighter(conf) {
|
||||
let highlighter;
|
||||
let highlighterPath;
|
||||
|
||||
switch (typeof conf.highlight) {
|
||||
case 'string':
|
||||
highlighterPath = path.getResourcePath(conf.highlight);
|
||||
|
||||
if (highlighterPath) {
|
||||
highlighter = require(highlighterPath).highlight;
|
||||
|
||||
if (typeof highlighter !== 'function') {
|
||||
logger.error('The syntax highlighting module "%s" does not assign a method ' +
|
||||
'to exports.highlight. Using the default syntax highlighter.',
|
||||
conf.highlight);
|
||||
highlighter = highlight;
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.error('Unable to find the syntax highlighting module "%s". Using the ' +
|
||||
'default syntax highlighter.', conf.highlight);
|
||||
highlighter = highlight;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'function':
|
||||
highlighter = conf.highlight;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
highlighter = highlight;
|
||||
}
|
||||
|
||||
return highlighter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
let highlighter;
|
||||
let parserFunction;
|
||||
let renderer;
|
||||
|
||||
conf = conf || {};
|
||||
highlighter = getHighlighter(conf);
|
||||
|
||||
switch (parserName) {
|
||||
case parserNames.marked:
|
||||
if (conf.hardwrap) {
|
||||
marked.setOptions({breaks: true});
|
||||
}
|
||||
|
||||
// Marked generates an "id" attribute for headers; this custom renderer suppresses it
|
||||
renderer = new marked.Renderer();
|
||||
|
||||
if (!conf.idInHeadings) {
|
||||
renderer.heading = (text, level) => util.format('<h%s>%s</h%s>', level, text, level);
|
||||
}
|
||||
|
||||
renderer.code = highlighter;
|
||||
|
||||
parserFunction = source => {
|
||||
let result;
|
||||
|
||||
source = escapeUnderscores(source);
|
||||
source = escapeUrls(source);
|
||||
|
||||
result = marked(source, { renderer: renderer })
|
||||
.replace(/\s+$/, '')
|
||||
.replace(/'/g, "'");
|
||||
|
||||
result = unescapeUrls(result);
|
||||
result = unencodeQuotes(result);
|
||||
|
||||
return result;
|
||||
};
|
||||
parserFunction._parser = parserNames.marked;
|
||||
|
||||
return parserFunction;
|
||||
|
||||
case parserNames.markdownit:
|
||||
renderer = new MarkdownIt({
|
||||
breaks: Boolean(conf.hardwrap),
|
||||
highlight: highlighter,
|
||||
html: true
|
||||
});
|
||||
|
||||
if (conf.idInHeadings) {
|
||||
renderer.use(mda, { tabIndex: false });
|
||||
}
|
||||
|
||||
parserFunction = source => {
|
||||
let result;
|
||||
|
||||
source = escapeUrls(source);
|
||||
source = escapeInlineTagBackslashes(source);
|
||||
|
||||
result = renderer.render(source)
|
||||
.replace(/\s+$/, '')
|
||||
.replace(/'/g, "'");
|
||||
|
||||
result = unescapeUrls(result);
|
||||
result = unencodeQuotes(result);
|
||||
|
||||
return result;
|
||||
};
|
||||
parserFunction._parser = parserNames.markdownit;
|
||||
|
||||
return parserFunction;
|
||||
|
||||
default:
|
||||
logger.error('Unrecognized Markdown parser "%s". Markdown support is disabled.',
|
||||
parserName);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = () => {
|
||||
const conf = env.conf.markdown;
|
||||
const parser = (conf && conf.parser) ? parserNames[conf.parser] : parserNames.markdownit;
|
||||
|
||||
return getParseFunction(parser, conf);
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Module to strip the leading BOM, if present, from UTF-8 files.
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Strip the leading BOM, if present, from a string.
|
||||
*
|
||||
* @private
|
||||
* @param {string} text - The string to strip.
|
||||
* @return {string} The stripped string.
|
||||
*/
|
||||
exports.strip = (text = '') => text.replace(/^\uFEFF/, '');
|
||||
+1025
File diff suppressed because it is too large
Load Diff
+65
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "jsdoc",
|
||||
"version": "4.0.3",
|
||||
"revision": "1714619656778",
|
||||
"description": "An API documentation generator for JavaScript.",
|
||||
"keywords": [
|
||||
"documentation",
|
||||
"javascript"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jsdoc/jsdoc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.20.15",
|
||||
"@jsdoc/salty": "^0.2.1",
|
||||
"@types/markdown-it": "^14.1.1",
|
||||
"bluebird": "^3.7.2",
|
||||
"catharsis": "^0.9.0",
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"js2xmlparser": "^4.0.2",
|
||||
"klaw": "^3.0.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-anchor": "^8.6.7",
|
||||
"marked": "^4.0.10",
|
||||
"mkdirp": "^1.0.4",
|
||||
"requizzle": "^0.2.3",
|
||||
"strip-json-comments": "^3.1.0",
|
||||
"underscore": "~1.13.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ajv": "^6.12.0",
|
||||
"gulp": "^5.0.0",
|
||||
"gulp-eslint": "^6.0.0",
|
||||
"gulp-json-editor": "^2.5.6",
|
||||
"nyc": "^15.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "gulp lint; gulp test"
|
||||
},
|
||||
"bin": {
|
||||
"jsdoc": "./jsdoc.js"
|
||||
},
|
||||
"bugs": "https://github.com/jsdoc/jsdoc/issues",
|
||||
"author": {
|
||||
"name": "Michael Mathews",
|
||||
"email": "micmath@gmail.com"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "The JSDoc Contributors",
|
||||
"url": "https://github.com/jsdoc/jsdoc/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Jeff Williams",
|
||||
"email": "jeffrey.l.williams@gmail.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable spaced-comment */
|
||||
/**
|
||||
* Demonstrate how to modify the source code before the parser sees it.
|
||||
*
|
||||
* @module plugins/commentConvert
|
||||
*/
|
||||
exports.handlers = {
|
||||
///
|
||||
/// Convert ///-style comments into jsdoc comments.
|
||||
/// @param e
|
||||
/// @param e.filename
|
||||
/// @param e.source
|
||||
///
|
||||
beforeParse(e) {
|
||||
e.source = e.source.replace(/(\n[ \t]*\/\/\/[^\n]*)+/g, $ => {
|
||||
const replacement = `\n/**${$.replace(/^[ \t]*\/\/\//mg, '').replace(/(\n$|$)/, '*/$1')}`;
|
||||
|
||||
return replacement;
|
||||
});
|
||||
}
|
||||
};
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
exports.handlers = {
|
||||
beforeParse(e) {
|
||||
// a JSDoc comment looks like: /**[one or more chars]*/
|
||||
const comments = e.source.match(/\/\*\*[\s\S]+?\*\//g);
|
||||
|
||||
if (comments) {
|
||||
e.source = comments.join('\n\n');
|
||||
} else {
|
||||
e.source = ''; // If file has no comments, parser should still receive no code
|
||||
}
|
||||
}
|
||||
};
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Escape HTML tags in descriptions.
|
||||
*
|
||||
* @module plugins/escapeHtml
|
||||
*/
|
||||
exports.handlers = {
|
||||
/**
|
||||
* Translate HTML tags in descriptions into safe entities. Replaces <, & and newlines
|
||||
*/
|
||||
newDoclet({doclet}) {
|
||||
if (doclet.description) {
|
||||
doclet.description = doclet.description
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/\r\n|\n|\r/g, '<br>');
|
||||
}
|
||||
}
|
||||
};
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Dump information about parser events to the console.
|
||||
*
|
||||
* @module plugins/eventDumper
|
||||
*/
|
||||
const _ = require('underscore');
|
||||
const doop = require('jsdoc/util/doop');
|
||||
const dump = require('jsdoc/util/dumper').dump;
|
||||
const env = require('jsdoc/env');
|
||||
const util = require('util');
|
||||
|
||||
const conf = env.conf.eventDumper || {};
|
||||
|
||||
// Dump the included parser events (defaults to all events)
|
||||
let 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const 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) {
|
||||
let result = {};
|
||||
|
||||
Object.keys(e).forEach(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') {
|
||||
result[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(eventType => {
|
||||
exports.handlers[eventType] = e => {
|
||||
console.log( dump({
|
||||
type: eventType,
|
||||
content: cleanse(e)
|
||||
}) );
|
||||
};
|
||||
});
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Translate doclet descriptions from Markdown into HTML.
|
||||
*
|
||||
* @module plugins/markdown
|
||||
*/
|
||||
const env = require('jsdoc/env');
|
||||
|
||||
const config = env.conf.markdown || {};
|
||||
const defaultTags = [
|
||||
'author',
|
||||
'classdesc',
|
||||
'description',
|
||||
'exceptions',
|
||||
'params',
|
||||
'properties',
|
||||
'returns',
|
||||
'see',
|
||||
'summary'
|
||||
];
|
||||
const hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
const parse = require('jsdoc/util/markdown').getParser();
|
||||
let tags = [];
|
||||
let excludeTags = [];
|
||||
|
||||
function shouldProcessString(tagName, text) {
|
||||
let shouldProcess = true;
|
||||
|
||||
// we only want to process `@author` and `@see` tags that contain Markdown links
|
||||
if ( (tagName === 'author' || tagName === 'see') && !text.includes('[') ) {
|
||||
shouldProcess = false;
|
||||
}
|
||||
|
||||
return shouldProcess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the markdown source in a doclet. The properties that should be processed are
|
||||
* configurable, but always include "author", "classdesc", "description", "exceptions", "params",
|
||||
* "properties", "returns", and "see". Handled properties can be bare strings, objects, or arrays
|
||||
* of objects.
|
||||
*/
|
||||
function process(doclet) {
|
||||
tags.forEach(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((value, index, original) => {
|
||||
const 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(tag => {
|
||||
if (!excludeTags.includes(tag) && !tags.includes(tag)) {
|
||||
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({doclet}) {
|
||||
process(doclet);
|
||||
}
|
||||
};
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
// lookup table of function doclets by longname
|
||||
let functionDoclets;
|
||||
|
||||
function hasUniqueValues(obj) {
|
||||
let isUnique = true;
|
||||
const seen = [];
|
||||
|
||||
Object.keys(obj).forEach(key => {
|
||||
if (seen.includes(obj[key])) {
|
||||
isUnique = false;
|
||||
}
|
||||
|
||||
seen.push(obj[key]);
|
||||
});
|
||||
|
||||
return isUnique;
|
||||
}
|
||||
|
||||
function getParamNames(params) {
|
||||
const names = [];
|
||||
|
||||
params.forEach(param => {
|
||||
let name = param.name || '';
|
||||
|
||||
if (param.variable) {
|
||||
name = `...${name}`;
|
||||
}
|
||||
if (name !== '') {
|
||||
names.push(name);
|
||||
}
|
||||
});
|
||||
|
||||
return names.length ? names.join(', ') : '';
|
||||
}
|
||||
|
||||
function getParamVariation({params}) {
|
||||
return getParamNames(params || []);
|
||||
}
|
||||
|
||||
function getUniqueVariations(doclets) {
|
||||
let counter = 0;
|
||||
const variations = {};
|
||||
const docletKeys = Object.keys(doclets);
|
||||
|
||||
function getUniqueNumbers() {
|
||||
docletKeys.forEach(doclet => {
|
||||
let newLongname;
|
||||
|
||||
while (true) {
|
||||
counter++;
|
||||
variations[doclet] = String(counter);
|
||||
|
||||
// is this longname + variation unique?
|
||||
newLongname = `${doclets[doclet].longname}(${variations[doclet]})`;
|
||||
if ( !functionDoclets[newLongname] ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getUniqueNames() {
|
||||
// start by trying to preserve existing variations
|
||||
docletKeys.forEach(doclet => {
|
||||
variations[doclet] = doclets[doclet].variation || getParamVariation(doclets[doclet]);
|
||||
});
|
||||
|
||||
// if they're identical, try again, without preserving existing variations
|
||||
if ( !hasUniqueValues(variations) ) {
|
||||
docletKeys.forEach(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) {
|
||||
const doclets = {
|
||||
oldDoclet: functionDoclets[newDoclet.longname],
|
||||
newDoclet: newDoclet
|
||||
};
|
||||
const docletKeys = Object.keys(doclets);
|
||||
let oldDocletLongname;
|
||||
let 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(doclet => {
|
||||
doclets[doclet].longname = doclets[doclet].longname.replace(/\([\s\S]*\)$/, '');
|
||||
doclets[doclet].variation = null;
|
||||
});
|
||||
}
|
||||
|
||||
variations = getUniqueVariations(doclets);
|
||||
|
||||
// update the longnames/variations
|
||||
docletKeys.forEach(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() {
|
||||
functionDoclets = {};
|
||||
},
|
||||
|
||||
newDoclet(e) {
|
||||
if (e.doclet.kind === 'function') {
|
||||
e.doclet = ensureUniqueLongname(e.doclet);
|
||||
}
|
||||
},
|
||||
|
||||
parseComplete() {
|
||||
functionDoclets = null;
|
||||
}
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Adds support for reusable partial jsdoc files.
|
||||
*
|
||||
* @module plugins/partial
|
||||
*/
|
||||
const env = require('jsdoc/env');
|
||||
const fs = require('jsdoc/fs');
|
||||
const path = require('path');
|
||||
|
||||
exports.handlers = {
|
||||
/**
|
||||
* Include a partial jsdoc
|
||||
*
|
||||
* @param e
|
||||
* @param e.filename
|
||||
* @param e.source
|
||||
* @example
|
||||
* @partial "partial_doc.jsdoc"
|
||||
*/
|
||||
beforeParse(e) {
|
||||
e.source = e.source.replace(/(@partial ".*")+/g, $ => {
|
||||
const pathArg = $.match(/".*"/)[0].replace(/"/g, '');
|
||||
const fullPath = path.join(e.filename, '..', pathArg);
|
||||
|
||||
const partialData = fs.readFileSync(fullPath, env.opts.encoding);
|
||||
|
||||
return partialData;
|
||||
});
|
||||
}
|
||||
};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Strips the rails template tags from a js.erb file
|
||||
*
|
||||
* @module plugins/railsTemplate
|
||||
*/
|
||||
exports.handlers = {
|
||||
/**
|
||||
* Remove rails tags from the source input (e.g. <% foo bar %>)
|
||||
*
|
||||
* @param e
|
||||
* @param e.filename
|
||||
* @param e.source
|
||||
*/
|
||||
beforeParse(e) {
|
||||
if (e.filename.match(/\.erb$/)) {
|
||||
e.source = e.source.replace(/<%.*%>/g, '');
|
||||
}
|
||||
}
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* This is just an example.
|
||||
*
|
||||
* @module plugins/shout
|
||||
*/
|
||||
exports.handlers = {
|
||||
/**
|
||||
* Make your descriptions more shoutier.
|
||||
*/
|
||||
newDoclet({doclet}) {
|
||||
if (typeof doclet.description === 'string') {
|
||||
doclet.description = doclet.description.toUpperCase();
|
||||
}
|
||||
}
|
||||
};
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @module plugins/sourcetag
|
||||
*/
|
||||
const 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": 9 }
|
||||
*/
|
||||
newDoclet({doclet}) {
|
||||
let tags = doclet.tags;
|
||||
let tag;
|
||||
let value;
|
||||
|
||||
// any user-defined tags in this doclet?
|
||||
if (typeof tags !== 'undefined') {
|
||||
// only interested in the @source tags
|
||||
tags = tags.filter(({title}) => title === 'source');
|
||||
|
||||
if (tags.length) {
|
||||
// take the first one
|
||||
tag = tags[0];
|
||||
|
||||
try {
|
||||
value = JSON.parse(tag.value);
|
||||
}
|
||||
catch (ex) {
|
||||
logger.error('@source tag expects a valid JSON value, like { "filename": "myfile.js", "lineno": 123 }.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
doclet.meta = doclet.meta || {};
|
||||
doclet.meta.filename = value.filename || '';
|
||||
doclet.meta.lineno = value.lineno || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* This plugin creates a summary tag, if missing, from the first sentence in the description.
|
||||
*
|
||||
* @module plugins/summarize
|
||||
*/
|
||||
exports.handlers = {
|
||||
/**
|
||||
* Autogenerate summaries, if missing, from the description, if present.
|
||||
*/
|
||||
newDoclet({doclet}) {
|
||||
let endTag;
|
||||
let tags;
|
||||
let stack;
|
||||
|
||||
// If the summary is missing, grab the first sentence from the description
|
||||
// and use that.
|
||||
if (doclet && !doclet.summary && doclet.description) {
|
||||
// The summary may end with `.$`, `. `, or `.<` (a period followed by an HTML tag).
|
||||
doclet.summary = doclet.description.split(/\.$|\.\s|\.</)[0];
|
||||
// Append `.` as it was removed in both cases, or is possibly missing.
|
||||
doclet.summary += '.';
|
||||
|
||||
// This is an excerpt of something that is possibly HTML.
|
||||
// Balance it using a stack. Assume it was initially balanced.
|
||||
tags = doclet.summary.match(/<[^>]+>/g) || [];
|
||||
stack = [];
|
||||
|
||||
tags.forEach(tag => {
|
||||
const 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
|
||||
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
|
||||
doclet.summary = doclet.summary.replace(/^<p>(.*)<\/p>$/i, '$1');
|
||||
}
|
||||
}
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @see [Nowhere](http://nowhere.com)
|
||||
*/
|
||||
function foo() {}
|
||||
|
||||
/**
|
||||
* @see AnObject#myProperty
|
||||
*/
|
||||
function bar() {}
|
||||
|
||||
/**
|
||||
* @author [Mr. Macintosh](http://www.folklore.org/StoryView.py?story=Mister_Macintosh.txt)
|
||||
* @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)
|
||||
* @summary My class.
|
||||
*/
|
||||
function MyClass(myParam) {
|
||||
this.value = myParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* "See" {@link chat."#channel"."say-\"hello\""}.
|
||||
*/
|
||||
function MyOtherClass() {}
|
||||
+50
@@ -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) {};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Strips the rails template tags from a js.erb file
|
||||
*
|
||||
* @module plugins/railsTemplate
|
||||
*/
|
||||
|
||||
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, "");
|
||||
}
|
||||
}
|
||||
};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
/** This doclet will be shown by default, just like normal. */
|
||||
function normal() {}
|
||||
|
||||
/** This doclet will be hidden by default because it begins with an underscore. */
|
||||
function _hidden() {}
|
||||
|
||||
/**
|
||||
* Klass class
|
||||
* @class
|
||||
*/
|
||||
function Klass() {
|
||||
/** This is a private property of the class, and should not. */
|
||||
this._privateProp = null;
|
||||
|
||||
/**
|
||||
* This is a property explicitly marked as private.
|
||||
* @private
|
||||
*/
|
||||
this.privateProp = null;
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
describe('commentConvert plugin', function() {
|
||||
var env = require('jsdoc/env');
|
||||
var path = require('jsdoc/path');
|
||||
|
||||
var docSet;
|
||||
var parser = jasmine.createParser();
|
||||
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);
|
||||
});
|
||||
});
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
describe('escapeHtml plugin', function() {
|
||||
var env = require('jsdoc/env');
|
||||
var path = require('jsdoc/path');
|
||||
|
||||
var docSet;
|
||||
var parser = jasmine.createParser();
|
||||
var pluginPath = 'plugins/escapeHtml';
|
||||
var pluginPathResolved = path.join(env.dirname, pluginPath);
|
||||
|
||||
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. Replaces <, & and newlines');
|
||||
});
|
||||
});
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
var env = require('jsdoc/env');
|
||||
var path = require('jsdoc/path');
|
||||
|
||||
describe('markdown plugin', function() {
|
||||
var pluginPath = 'plugins/markdown';
|
||||
var pluginPathResolved = path.join(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.author[0],
|
||||
myClass.classdesc,
|
||||
myClass.description,
|
||||
myClass.exceptions[0].description,
|
||||
myClass.params[0].description,
|
||||
myClass.properties[0].description,
|
||||
myClass.returns[0].description,
|
||||
myClass.see,
|
||||
myClass.summary
|
||||
].forEach(function(value) {
|
||||
// if we processed the value, it should be wrapped in a <p> tag
|
||||
expect( /^<p>(?:.+)<\/p>$/.test(value) ).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should unescape " entities in inline tags, but not elsewhere', function() {
|
||||
var myOtherClass = docSet.getByLongname('MyOtherClass')[0];
|
||||
|
||||
plugin.handlers.newDoclet({ doclet: myOtherClass });
|
||||
|
||||
expect(myOtherClass.description).toContain('chat."#channel"."say-\\"hello\\""');
|
||||
expect(myOtherClass.description).toContain('"See"');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
describe('plugins/overloadHelper', function() {
|
||||
var env = require('jsdoc/env');
|
||||
var path = require('jsdoc/path');
|
||||
|
||||
var docSet;
|
||||
var parser = jasmine.createParser();
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
describe('railsTemplate plugin', function() {
|
||||
var env = require('jsdoc/env');
|
||||
var path = require('jsdoc/path');
|
||||
|
||||
var parser = jasmine.createParser();
|
||||
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. )');
|
||||
});
|
||||
});
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
describe('shout plugin', function() {
|
||||
var env = require('jsdoc/env');
|
||||
var path = require('jsdoc/path');
|
||||
|
||||
var docSet;
|
||||
var parser = jasmine.createParser();
|
||||
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.');
|
||||
});
|
||||
});
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
describe('sourcetag plugin', function() {
|
||||
var env = require('jsdoc/env');
|
||||
var path = require('jsdoc/path');
|
||||
|
||||
var docSet;
|
||||
var parser = jasmine.createParser();
|
||||
var pluginPath = 'plugins/sourcetag';
|
||||
var pluginPathResolved = path.join(env.dirname, pluginPath);
|
||||
|
||||
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(9);
|
||||
});
|
||||
});
|
||||
+112
@@ -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.');
|
||||
});
|
||||
});
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
describe('underscore plugin', function () {
|
||||
var env = require('jsdoc/env');
|
||||
var path = require('jsdoc/path');
|
||||
|
||||
var docSet;
|
||||
var parser = jasmine.createParser();
|
||||
var pluginPath = 'plugins/underscore';
|
||||
var fixturePath = 'plugins/test/fixtures/underscore';
|
||||
var pluginPathResolved = path.join(env.dirname, pluginPath);
|
||||
var plugin = require(pluginPathResolved);
|
||||
|
||||
require('jsdoc/plugins').installPlugins([pluginPathResolved], parser);
|
||||
docSet = jasmine.getDocSetFromFile(fixturePath + '.js', parser);
|
||||
|
||||
it('should not mark normal, public properties as private', function() {
|
||||
// Base line tests
|
||||
var normal = docSet.getByLongname('normal');
|
||||
expect(normal[0].access).toBeUndefined();
|
||||
|
||||
var realPrivate = docSet.getByLongname('Klass#privateProp');
|
||||
expect(realPrivate[0].access).toEqual('private');
|
||||
});
|
||||
|
||||
it('should hide doclet for symbols beginning with an underscore under normal circumstances', function () {
|
||||
var hidden = docSet.getByLongname('_hidden');
|
||||
expect(hidden[0].access).toEqual('private');
|
||||
});
|
||||
|
||||
it('picks up "this"', function() {
|
||||
var privateUnderscore = docSet.getByLongname('Klass#_privateProp');
|
||||
expect(privateUnderscore[0].access).toEqual('private');
|
||||
});
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Removes all symbols that begin with an underscore from the doc output. If
|
||||
* you're using underscores to denote private variables in modules, this
|
||||
* automatically hides them.
|
||||
*
|
||||
* @module plugins/underscore
|
||||
*/
|
||||
|
||||
exports.handlers = {
|
||||
newDoclet({doclet}) {
|
||||
// Ignore comment blocks for all symbols that begin with underscore
|
||||
if (doclet.name.charAt(0) === '_' || doclet.name.substr(0, 6) === 'this._') {
|
||||
doclet.access = 'private';
|
||||
}
|
||||
}
|
||||
};
|
||||
+27
@@ -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
|
||||
````
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
## Generating Typeface Fonts
|
||||
|
||||
The default template uses the [OpenSans](https://www.google.com/fonts/specimen/Open+Sans) typeface. The font files can be regenerated 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.
|
||||
+692
@@ -0,0 +1,692 @@
|
||||
const doop = require('jsdoc/util/doop');
|
||||
const env = require('jsdoc/env');
|
||||
const fs = require('jsdoc/fs');
|
||||
const helper = require('jsdoc/util/templateHelper');
|
||||
const logger = require('jsdoc/util/logger');
|
||||
const path = require('jsdoc/path');
|
||||
const { taffy } = require('@jsdoc/salty');
|
||||
const template = require('jsdoc/template');
|
||||
const util = require('util');
|
||||
|
||||
const htmlsafe = helper.htmlsafe;
|
||||
const linkto = helper.linkto;
|
||||
const resolveAuthorLinks = helper.resolveAuthorLinks;
|
||||
const hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
|
||||
let data;
|
||||
let view;
|
||||
|
||||
let 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) {
|
||||
let url;
|
||||
|
||||
if ( !/^(#.+)/.test(hash) ) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
url = helper.createLink(doclet);
|
||||
url = url.replace(/(#.+|$)/, hash);
|
||||
|
||||
return `<a href="${url}">${hash}</a>`;
|
||||
}
|
||||
|
||||
function needsSignature({kind, type, meta}) {
|
||||
let needsSig = false;
|
||||
|
||||
// function and class definitions always get a signature
|
||||
if (kind === 'function' || kind === 'class') {
|
||||
needsSig = true;
|
||||
}
|
||||
// typedefs that contain functions get a signature, too
|
||||
else if (kind === 'typedef' && type && type.names &&
|
||||
type.names.length) {
|
||||
for (let i = 0, l = type.names.length; i < l; i++) {
|
||||
if (type.names[i].toLowerCase() === 'function') {
|
||||
needsSig = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// and namespaces that are functions get a signature (but finding them is a
|
||||
// bit messy)
|
||||
else if (kind === 'namespace' && meta && meta.code &&
|
||||
meta.code.type && meta.code.type.match(/[Ff]unction/)) {
|
||||
needsSig = true;
|
||||
}
|
||||
|
||||
return needsSig;
|
||||
}
|
||||
|
||||
function getSignatureAttributes({optional, nullable}) {
|
||||
const attributes = [];
|
||||
|
||||
if (optional) {
|
||||
attributes.push('opt');
|
||||
}
|
||||
|
||||
if (nullable === true) {
|
||||
attributes.push('nullable');
|
||||
}
|
||||
else if (nullable === false) {
|
||||
attributes.push('non-null');
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
function updateItemName(item) {
|
||||
const attributes = getSignatureAttributes(item);
|
||||
let 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(({name}) => name && !name.includes('.')).map(updateItemName);
|
||||
}
|
||||
|
||||
function buildItemTypeStrings(item) {
|
||||
const types = [];
|
||||
|
||||
if (item && item.type && item.type.names) {
|
||||
item.type.names.forEach(name => {
|
||||
types.push( linkto(name, htmlsafe(name)) );
|
||||
});
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
function buildAttribsString(attribs) {
|
||||
let attribsString = '';
|
||||
|
||||
if (attribs && attribs.length) {
|
||||
attribsString = htmlsafe( util.format('(%s) ', attribs.join(', ')) );
|
||||
}
|
||||
|
||||
return attribsString;
|
||||
}
|
||||
|
||||
function addNonParamAttributes(items) {
|
||||
let types = [];
|
||||
|
||||
items.forEach(item => {
|
||||
types = types.concat( buildItemTypeStrings(item) );
|
||||
});
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
function addSignatureParams(f) {
|
||||
const params = f.params ? addParamAttributes(f.params) : [];
|
||||
|
||||
f.signature = util.format( '%s(%s)', (f.signature || ''), params.join(', ') );
|
||||
}
|
||||
|
||||
function addSignatureReturns(f) {
|
||||
const attribs = [];
|
||||
let attribsString = '';
|
||||
let returnTypes = [];
|
||||
let returnTypesString = '';
|
||||
const source = f.yields || f.returns;
|
||||
|
||||
// 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 (source) {
|
||||
source.forEach(item => {
|
||||
helper.getAttribs(item).forEach(attrib => {
|
||||
if (!attribs.includes(attrib)) {
|
||||
attribs.push(attrib);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
attribsString = buildAttribsString(attribs);
|
||||
}
|
||||
|
||||
if (source) {
|
||||
returnTypes = addNonParamAttributes(source);
|
||||
}
|
||||
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) {
|
||||
const types = f.type ? buildItemTypeStrings(f) : [];
|
||||
|
||||
f.signature = `${f.signature || ''}<span class="type-signature">${types.length ? ` :${types.join('|')}` : ''}</span>`;
|
||||
}
|
||||
|
||||
function addAttribs(f) {
|
||||
const attribs = helper.getAttribs(f);
|
||||
const attribsString = buildAttribsString(attribs);
|
||||
|
||||
f.attribs = util.format('<span class="type-signature">%s</span>', attribsString);
|
||||
}
|
||||
|
||||
function shortenPaths(files, commonPrefix) {
|
||||
Object.keys(files).forEach(file => {
|
||||
files[file].shortened = files[file].resolved.replace(commonPrefix, '')
|
||||
// always use forward slashes
|
||||
.replace(/\\/g, '/');
|
||||
});
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function getPathFromDoclet({meta}) {
|
||||
if (!meta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return meta.path && meta.path !== 'null' ?
|
||||
path.join(meta.path, meta.filename) :
|
||||
meta.filename;
|
||||
}
|
||||
|
||||
function generate(title, docs, filename, resolveLinks) {
|
||||
let docData;
|
||||
let html;
|
||||
let outpath;
|
||||
|
||||
resolveLinks = resolveLinks !== false;
|
||||
|
||||
docData = {
|
||||
env: env,
|
||||
title: title,
|
||||
docs: docs
|
||||
};
|
||||
|
||||
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 = 'utf8') {
|
||||
Object.keys(sourceFiles).forEach(file => {
|
||||
let source;
|
||||
// links are keyed to the shortened path in each doclet's `meta.shortpath` property
|
||||
const 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) {
|
||||
const symbols = {};
|
||||
|
||||
// build a lookup table
|
||||
doclets.forEach(symbol => {
|
||||
symbols[symbol.longname] = symbols[symbol.longname] || [];
|
||||
symbols[symbol.longname].push(symbol);
|
||||
});
|
||||
|
||||
modules.forEach(module => {
|
||||
if (symbols[module.longname]) {
|
||||
module.modules = symbols[module.longname]
|
||||
// Only show symbols that have a description. Make an exception for classes, because
|
||||
// we want to show the constructor-signature heading no matter what.
|
||||
.filter(({description, kind}) => description || kind === 'class')
|
||||
.map(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, linktoFn) {
|
||||
let nav = '';
|
||||
|
||||
if (items.length) {
|
||||
let itemsNav = '';
|
||||
|
||||
items.forEach(item => {
|
||||
let displayName;
|
||||
|
||||
if ( !hasOwnProp.call(item, 'longname') ) {
|
||||
itemsNav += `<li>${linktoFn('', item.name)}</li>`;
|
||||
}
|
||||
else if ( !hasOwnProp.call(itemsSeen, item.longname) ) {
|
||||
if (env.conf.templates.default.useLongnameInNav) {
|
||||
displayName = item.longname;
|
||||
} else {
|
||||
displayName = item.name;
|
||||
}
|
||||
itemsNav += `<li>${linktoFn(item.longname, displayName.replace(/\b(module|event):/g, ''))}</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) {
|
||||
let globalNav;
|
||||
let nav = '<h2><a href="index.html">Home</a></h2>';
|
||||
const seen = {};
|
||||
const seenTutorials = {};
|
||||
|
||||
nav += buildMemberNav(members.modules, 'Modules', {}, linkto);
|
||||
nav += buildMemberNav(members.externals, 'Externals', seen, linktoExternal);
|
||||
nav += buildMemberNav(members.namespaces, 'Namespaces', seen, linkto);
|
||||
nav += buildMemberNav(members.classes, 'Classes', seen, linkto);
|
||||
nav += buildMemberNav(members.interfaces, 'Interfaces', seen, linkto);
|
||||
nav += buildMemberNav(members.events, 'Events', seen, linkto);
|
||||
nav += buildMemberNav(members.mixins, 'Mixins', seen, linkto);
|
||||
nav += buildMemberNav(members.tutorials, 'Tutorials', seenTutorials, linktoTutorial);
|
||||
|
||||
if (members.globals.length) {
|
||||
globalNav = '';
|
||||
|
||||
members.globals.forEach(({kind, longname, name}) => {
|
||||
if ( kind !== 'typedef' && !hasOwnProp.call(seen, longname) ) {
|
||||
globalNav += `<li>${linkto(longname, name)}</li>`;
|
||||
}
|
||||
seen[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 = (taffyData, opts, tutorials) => {
|
||||
let classes;
|
||||
let conf;
|
||||
let externals;
|
||||
let files;
|
||||
let fromDir;
|
||||
let globalUrl;
|
||||
let indexUrl;
|
||||
let interfaces;
|
||||
let members;
|
||||
let mixins;
|
||||
let modules;
|
||||
let namespaces;
|
||||
let outputSourceFiles;
|
||||
let packageInfo;
|
||||
let packages;
|
||||
const sourceFilePaths = [];
|
||||
let sourceFiles = {};
|
||||
let staticFileFilter;
|
||||
let staticFilePaths;
|
||||
let staticFiles;
|
||||
let staticFileScanner;
|
||||
let templatePath;
|
||||
|
||||
data = taffyData;
|
||||
|
||||
conf = env.conf.templates || {};
|
||||
conf.default = conf.default || {};
|
||||
|
||||
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
|
||||
indexUrl = helper.getUniqueFilename('index');
|
||||
// don't call registerLink() on this one! 'index' is also a valid longname
|
||||
|
||||
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);
|
||||
|
||||
data().each(doclet => {
|
||||
let sourcePath;
|
||||
|
||||
doclet.attribs = '';
|
||||
|
||||
if (doclet.examples) {
|
||||
doclet.examples = doclet.examples.map(example => {
|
||||
let caption;
|
||||
let 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((seeItem, i) => {
|
||||
doclet.see[i] = hashToLink(doclet, seeItem);
|
||||
});
|
||||
}
|
||||
|
||||
// build a list of source files
|
||||
if (doclet.meta) {
|
||||
sourcePath = getPathFromDoclet(doclet);
|
||||
sourceFiles[sourcePath] = {
|
||||
resolved: sourcePath,
|
||||
shortened: null
|
||||
};
|
||||
if (!sourceFilePaths.includes(sourcePath)) {
|
||||
sourceFilePaths.push(sourcePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// update outdir if necessary, then create outdir
|
||||
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
|
||||
fromDir = path.join(templatePath, 'static');
|
||||
staticFiles = fs.ls(fromDir, 3);
|
||||
|
||||
staticFiles.forEach(fileName => {
|
||||
const toDir = fs.toDir( fileName.replace(fromDir, outdir) );
|
||||
|
||||
fs.mkPath(toDir);
|
||||
fs.copyFileSync(fileName, toDir);
|
||||
});
|
||||
|
||||
// copy user-specified static files to outdir
|
||||
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(filePath => {
|
||||
let extraStaticFiles;
|
||||
|
||||
filePath = path.resolve(env.pwd, filePath);
|
||||
extraStaticFiles = staticFileScanner.scan([filePath], 10, staticFileFilter);
|
||||
|
||||
extraStaticFiles.forEach(fileName => {
|
||||
const sourcePath = fs.toDir(filePath);
|
||||
const 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(doclet => {
|
||||
let docletPath;
|
||||
const url = helper.createLink(doclet);
|
||||
|
||||
helper.registerLink(doclet.longname, url);
|
||||
|
||||
// add a shortened version of the full path
|
||||
if (doclet.meta) {
|
||||
docletPath = getPathFromDoclet(doclet);
|
||||
docletPath = sourceFiles[docletPath].shortened;
|
||||
if (docletPath) {
|
||||
doclet.meta.shortpath = docletPath;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
data().each(doclet => {
|
||||
const url = helper.longnameToUrl[doclet.longname];
|
||||
|
||||
if (url.includes('#')) {
|
||||
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(doclet => {
|
||||
doclet.ancestors = getAncestorLinks(doclet);
|
||||
|
||||
if (doclet.kind === 'member') {
|
||||
addSignatureTypes(doclet);
|
||||
addAttribs(doclet);
|
||||
}
|
||||
|
||||
if (doclet.kind === 'constant') {
|
||||
addSignatureTypes(doclet);
|
||||
addAttribs(doclet);
|
||||
doclet.kind = 'member';
|
||||
}
|
||||
});
|
||||
|
||||
members = helper.getMembers(data);
|
||||
members.tutorials = tutorials.children;
|
||||
|
||||
// output pretty-printed source files by default
|
||||
outputSourceFiles = conf.default && conf.default.outputSourceFiles !== 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
|
||||
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
|
||||
classes = taffy(members.classes);
|
||||
modules = taffy(members.modules);
|
||||
namespaces = taffy(members.namespaces);
|
||||
mixins = taffy(members.mixins);
|
||||
externals = taffy(members.externals);
|
||||
interfaces = taffy(members.interfaces);
|
||||
|
||||
Object.keys(helper.longnameToUrl).forEach(longname => {
|
||||
const myClasses = helper.find(classes, {longname: longname});
|
||||
const myExternals = helper.find(externals, {longname: longname});
|
||||
const myInterfaces = helper.find(interfaces, {longname: longname});
|
||||
const myMixins = helper.find(mixins, {longname: longname});
|
||||
const myModules = helper.find(modules, {longname: longname});
|
||||
const myNamespaces = helper.find(namespaces, {longname: longname});
|
||||
|
||||
if (myModules.length) {
|
||||
generate(`Module: ${myModules[0].name}`, myModules, helper.longnameToUrl[longname]);
|
||||
}
|
||||
|
||||
if (myClasses.length) {
|
||||
generate(`Class: ${myClasses[0].name}`, myClasses, helper.longnameToUrl[longname]);
|
||||
}
|
||||
|
||||
if (myNamespaces.length) {
|
||||
generate(`Namespace: ${myNamespaces[0].name}`, myNamespaces, helper.longnameToUrl[longname]);
|
||||
}
|
||||
|
||||
if (myMixins.length) {
|
||||
generate(`Mixin: ${myMixins[0].name}`, myMixins, helper.longnameToUrl[longname]);
|
||||
}
|
||||
|
||||
if (myExternals.length) {
|
||||
generate(`External: ${myExternals[0].name}`, myExternals, helper.longnameToUrl[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) {
|
||||
const tutorialData = {
|
||||
title: title,
|
||||
header: tutorial.title,
|
||||
content: tutorial.parse(),
|
||||
children: tutorial.children
|
||||
};
|
||||
const tutorialPath = path.join(outdir, filename);
|
||||
let 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({children}) {
|
||||
children.forEach(child => {
|
||||
generateTutorial(`Tutorial: ${child.title}`, child, helper.tutorialToUrl(child.name));
|
||||
saveChildren(child);
|
||||
});
|
||||
}
|
||||
|
||||
saveChildren(tutorials);
|
||||
};
|
||||
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+1830
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 116 KiB |
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+1830
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 118 KiB |
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+1830
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 120 KiB |
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+1831
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 114 KiB |
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+1835
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 120 KiB |
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+1831
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 117 KiB |
Generated
Vendored
BIN
Binary file not shown.
+25
@@ -0,0 +1,25 @@
|
||||
/*global document */
|
||||
(() => {
|
||||
const source = document.getElementsByClassName('prettyprint source linenums');
|
||||
let i = 0;
|
||||
let lineNumber = 0;
|
||||
let lineId;
|
||||
let lines;
|
||||
let totalLines;
|
||||
let 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';
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
Generated
Vendored
+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.
|
||||
+2
@@ -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"]);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user