├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github ├── dependabot.yml ├── documentation-js-logo.png └── workflows │ ├── codeql.yml │ └── node.js.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── __tests__ ├── .eslintrc ├── __snapshots__ │ ├── bin-readme.js.snap │ ├── bin.js.snap │ ├── index.js.snap │ └── test.js.snap ├── bin-readme.js ├── bin.js ├── config_fixture │ ├── config │ ├── config.json │ ├── config.yaml │ ├── config.yml │ ├── config_comments.json │ ├── config_file.yml │ └── config_links.yml ├── fixture │ ├── _external-deps-included.json │ ├── _multi-file-input.json │ ├── auto_lang_hljs │ │ ├── config.yml │ │ ├── multilanguage.input.js │ │ └── multilanguage.output.md │ ├── bad │ │ ├── syntax.input │ │ └── syntax.output.json │ ├── boolean-literal-type.input.js │ ├── class.input.js │ ├── config-bad.yml │ ├── config-malformed.json │ ├── config.json │ ├── custom_theme │ │ └── index.js │ ├── default-export-function.input.js │ ├── document-exported-bad │ │ ├── exports-z.js │ │ └── x.js │ ├── document-exported-export-default-object.input.js │ ├── document-exported-export-default-value.input.js │ ├── document-exported.input.js │ ├── document-exported │ │ ├── x.js │ │ ├── y.js │ │ └── z.js │ ├── es6-class-property.input.js │ ├── es6-class.input.js │ ├── es6-default2.input.js │ ├── es6-ext.es6 │ ├── es6-import.input.js │ ├── es6.input.js │ ├── es6.output-toc.md │ ├── event.input.js │ ├── example-caption.input.js │ ├── extension.jsx │ ├── extension │ │ ├── extension-required.jsx │ │ └── index.otherextension │ ├── external.input.js │ ├── factory.input.js │ ├── flow-exported-opaque-type.input.js │ ├── flow-optional-chaining.input.js │ ├── flow-unnamed-params.input.js │ ├── flow │ │ └── comment-types.js │ ├── html │ │ ├── documentation.yml │ │ ├── nested.input.js │ │ ├── nested.output.files │ │ └── nested.output.json │ ├── import.meta.input.js │ ├── infer-private.input.js │ ├── inheritance.input.js │ ├── inline-link.input.js │ ├── interface.input.js │ ├── internal.input.js │ ├── lends.input.js │ ├── lint │ │ ├── lint.input.dependency.js │ │ ├── lint.input.js │ │ ├── lint.input.shallow.js │ │ └── lint.output │ ├── literal_types.input.js │ ├── memberedclass.input.js │ ├── merge-infered-type.input.js │ ├── meta.input.js │ ├── multisignature.input.js │ ├── nearby_params.input.js │ ├── nest_events.input.js │ ├── nest_params.input.js │ ├── newline-in-description.input.js │ ├── no-name.input.js │ ├── node_modules │ │ ├── external │ │ │ ├── index.js │ │ │ ├── lib │ │ │ │ └── main.js │ │ │ ├── node_modules │ │ │ │ └── external2 │ │ │ │ │ └── index.js │ │ │ └── package.json │ │ └── external2 │ │ │ └── index.js │ ├── optional-record-field-type.input.js │ ├── params.input.js │ ├── polyglot │ │ └── blend.json │ ├── react-jsx.input.js │ ├── readme │ │ ├── README.input.md │ │ ├── README.output.md │ │ └── index.js │ ├── require-json-no-extension.input.js │ ├── require-json.input.js │ ├── require-json.json │ ├── resolve │ │ ├── index.js │ │ ├── node.js │ │ └── package.json │ ├── sections.config.yml │ ├── sections.input.js │ ├── simple-callback.input.js │ ├── simple-hashbang.input.js │ ├── simple-private.input.js │ ├── simple-singlestar.input.js │ ├── simple-triplestar.input.js │ ├── simple-two.input.js │ ├── simple.config.yml │ ├── simple.input.js │ ├── snowflake.md │ ├── sort-order-alpha.input.js │ ├── sorting │ │ ├── input.js │ │ ├── output-bad.txt │ │ └── output.json │ ├── string-literal-key.input.js │ ├── this-class.input.js │ ├── type_application.input.js │ ├── var-function-param-return.input.js │ ├── vue-no-script.input.vue │ └── vue.input.vue ├── format_type.js ├── index.js ├── lib │ ├── __snapshots__ │ │ └── sort.js.snap │ ├── filter_access.js │ ├── flow_doctrine.js │ ├── git │ │ ├── find_git.js │ │ └── url_prefix.js │ ├── github.js │ ├── hierarchy.js │ ├── infer │ │ ├── __snapshots__ │ │ │ └── params.js.snap │ │ ├── access.js │ │ ├── augments.js │ │ ├── finders.js │ │ ├── implements.js │ │ ├── kind.js │ │ ├── membership.js │ │ ├── name.js │ │ ├── params.js │ │ ├── properties.js │ │ ├── return.js │ │ └── type.js │ ├── input │ │ ├── dependency.js │ │ └── shallow.js │ ├── lint.js │ ├── merge_config.js │ ├── module_filters.js │ ├── nest.js │ ├── output │ │ └── util │ │ │ └── formatters.js │ ├── parse.js │ ├── parsers │ │ ├── javascript.js │ │ └── parse_to_ast.js │ ├── sort.js │ ├── ts_doctrine.js │ └── walk.js ├── linker.js ├── misc │ └── package.json ├── test.js └── utils.js ├── bin └── documentation.js ├── declarations └── comment.js ├── docs ├── CONFIG.md ├── FAQ.md ├── GETTING_STARTED.md ├── NODE_API.md ├── POLYGLOT.md ├── RECIPES.md ├── THEMING.md ├── TROUBLESHOOTING.md ├── USAGE.md └── USAGE_NODE.md ├── package-lock.json ├── package.json ├── screenshorts └── show options.jpg └── src ├── commands ├── build.js ├── index.js ├── lint.js ├── readme.js └── shared_options.js ├── config.js ├── default_theme ├── README.md ├── assets │ ├── anchor.js │ ├── bass-addons.css │ ├── bass.css │ ├── fonts │ │ ├── EOT │ │ │ ├── SourceCodePro-Bold.eot │ │ │ └── SourceCodePro-Regular.eot │ │ ├── LICENSE.txt │ │ ├── OTF │ │ │ ├── SourceCodePro-Bold.otf │ │ │ └── SourceCodePro-Regular.otf │ │ ├── TTF │ │ │ ├── SourceCodePro-Bold.ttf │ │ │ └── SourceCodePro-Regular.ttf │ │ ├── WOFF │ │ │ ├── OTF │ │ │ │ ├── SourceCodePro-Bold.otf.woff │ │ │ │ └── SourceCodePro-Regular.otf.woff │ │ │ └── TTF │ │ │ │ ├── SourceCodePro-Bold.ttf.woff │ │ │ │ └── SourceCodePro-Regular.ttf.woff │ │ ├── WOFF2 │ │ │ ├── OTF │ │ │ │ ├── SourceCodePro-Bold.otf.woff2 │ │ │ │ └── SourceCodePro-Regular.otf.woff2 │ │ │ └── TTF │ │ │ │ ├── SourceCodePro-Bold.ttf.woff2 │ │ │ │ └── SourceCodePro-Regular.ttf.woff2 │ │ └── source-code-pro.css │ ├── github.css │ ├── site.js │ ├── split.css │ ├── split.js │ └── style.css ├── index._ ├── index.js ├── note._ ├── paramProperty._ ├── section._ └── section_list._ ├── extractors ├── comments.js └── exported.js ├── filter_access.js ├── flow_doctrine.js ├── garbage_collect.js ├── get-readme-file.js ├── git ├── find_git.js └── url_prefix.js ├── github.js ├── hierarchy.js ├── index.js ├── infer ├── access.js ├── augments.js ├── finders.js ├── implements.js ├── kind.js ├── membership.js ├── name.js ├── params.js ├── properties.js ├── return.js └── type.js ├── input ├── dependency.js ├── moduleDeps.js ├── readFileCode.js ├── shallow.js └── smart_glob.js ├── is_jsdoc_comment.js ├── lint.js ├── merge_config.js ├── module_filters.js ├── nest.js ├── output ├── highlighter.js ├── html.js ├── json.js ├── markdown.js ├── markdown_ast.js └── util │ ├── format_type.js │ ├── formatters.js │ ├── linker_stack.js │ └── reroute_links.js ├── parse.js ├── parsers ├── README.md ├── javascript.js └── parse_to_ast.js ├── remark-jsDoc-link.js ├── remark-parse.js ├── remark-remove-position.js ├── sort.js ├── ts_doctrine.js ├── type_annotation.js └── walk.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,css}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | __tests__/fixture/* 2 | src/default_theme/* 3 | coverage/* 4 | lib/* 5 | declarations/* 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaVersion": 2020 6 | }, 7 | "rules": { 8 | "no-var": 2, 9 | "prefer-const": 2, 10 | "no-use-before-define": [2, "nofunc"], 11 | "camelcase": 2, 12 | "no-lonely-if": 2, 13 | "no-else-return": 2, 14 | "new-cap": 2, 15 | "no-empty": 2, 16 | "consistent-return": 0, 17 | "no-new": 2, 18 | "object-shorthand": ["error", "always", { "avoidQuotes": true }], 19 | "no-throw-literal": 2, 20 | "no-self-compare": 2, 21 | "no-void": 2, 22 | "no-unused-vars": 2, 23 | "wrap-iife": 2, 24 | "no-eq-null": 2, 25 | "strict": [2, "global"], 26 | "no-shadow": 0, 27 | "no-undef": 2 28 | }, 29 | "extends": ["eslint:recommended", "prettier"], 30 | "env": { 31 | "node": true, 32 | "es6": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | test/fixture/polyglot/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/en/code-security/dependabot 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: github-actions 9 | directory: / 10 | schedule: 11 | interval: weekly 12 | - package-ecosystem: npm 13 | directory: / 14 | schedule: 15 | interval: weekly 16 | -------------------------------------------------------------------------------- /.github/documentation-js-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/.github/documentation-js-logo.png -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | schedule: 9 | - cron: '0 0 1 * *' 10 | workflow_dispatch: 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | security-events: write 18 | 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | language: [ 'javascript-typescript' ] 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v4 27 | 28 | # Initializes the CodeQL tools for scanning. 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v3 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v3 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v3 40 | with: 41 | category: "/language:${{matrix.language}}" 42 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [master] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [18.x, 20.x] 19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: 'npm' 28 | - run: npm ci 29 | - run: npm run test-ci 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | .nyc_output 3 | /node_modules 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run pre-commit 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .eslintignore 3 | .eslintrc 4 | .gitattributes 5 | .gitignore 6 | .nyc_output 7 | circle.yml 8 | coverage 9 | __tests__ 10 | .circleci 11 | .github 12 | .prettierignore 13 | .prettierrc 14 | CODE_OF_CONDUCT.md 15 | ISSUE_TEMPLATE.md 16 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | __tests__/fixture 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid" 5 | } 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team. All complaints will be reviewed and 59 | investigated and will result in a response that is deemed necessary and appropriate 60 | to the circumstances. The project team is obligated to maintain confidentiality with 61 | regard to the reporter of an incident. Further details of specific enforcement 62 | policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # documentation is an OPEN Open Source Project 2 | 3 | ----------------------------------------- 4 | 5 | ## What? 6 | 7 | Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 8 | 9 | ## Rules 10 | 11 | There are a few basic ground-rules for contributors: 12 | 13 | 1. **No `--force` pushes** or modifying the Git history in any way. 14 | 1. **Non-master branches** ought to be used for ongoing work. 15 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors. 16 | 1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 17 | 1. Contributors should attempt to adhere to the prevailing code-style. 18 | 19 | ## Releases 20 | 21 | Declaring formal releases remains the prerogative of the project maintainer. 22 | 23 | ## Changes to this arrangement 24 | 25 | This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. 26 | 27 | [this approach is totally cribbed from the excellent LevelUP project](https://github.com/Level/community/blob/master/CONTRIBUTING.md) 28 | 29 | ---- 30 | 31 | ## Releasing 32 | 33 | documentation aims to **release often**. We use [standard-changelog](https://github.com/conventional-changelog/standard-changelog) 34 | to generate CHANGELOG.md entries and [commitizen](https://github.com/commitizen/cz-cli) to standardize 35 | commit messages. Pull Request messages should be standardized to commitizen syntax (aka angular standard) 36 | before merge. 37 | 38 | Release process: 39 | 40 | * Confirm that `master` passes CI tests 41 | * Run `npm run release` or in case it's a prerelease you'd run i.e. `npm run release -- --prerelease alpha` 42 | * It will automatically update the version in package.json and make a git tag. 43 | * Push commits 44 | * npm publish 45 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **If you're reporting a bug, please include _input code_, _output documentation_, 2 | a description of what you expected to happen, and what happened instead.** 3 | 4 | * What version of documentation.js are you using?: 5 | * How are you running documentation.js (on the CLI, Node.js API, Grunt, other?): 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2025, documentationjs <> 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | -------------------------------------------------------------------------------- 18 | 19 | Contains sections of eslint 20 | 21 | ESLint 22 | Copyright JS Foundation and other contributors, https://js.foundation 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy 25 | of this software and associated documentation files (the "Software"), to deal 26 | in the Software without restriction, including without limitation the rights 27 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | copies of the Software, and to permit persons to whom the Software is 29 | furnished to do so, subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in 32 | all copies or substantial portions of the Software. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 36 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 37 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 38 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 39 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 40 | THE SOFTWARE. 41 | 42 | -------------------------------------------------------------------------------- 43 | 44 | Contains sections of prettier 45 | 46 | Copyright © James Long and contributors 47 | 48 | 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: 49 | 50 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 51 | 52 | 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. -------------------------------------------------------------------------------- /__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "valid-jsdoc": [0], 4 | "no-unused-vars": [0] 5 | }, 6 | "env": { 7 | "jest": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/bin-readme.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`readme autodetection of different filenames updates readme.markdown 1`] = ` 4 | "# A title 5 | 6 | # API 7 | 8 | 9 | 10 | ### Table of Contents 11 | 12 | * [foo](#foo) 13 | * [Parameters](#parameters) 14 | * [bar](#bar) 15 | * [Parameters](#parameters-1) 16 | 17 | ## foo 18 | 19 | A function with documentation. 20 | 21 | ### Parameters 22 | 23 | * \`a\` {string} blah 24 | 25 | Returns **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global\\\\_Objects/Number)** answer 26 | 27 | ## bar 28 | 29 | A second function with docs 30 | 31 | ### Parameters 32 | 33 | * \`b\` 34 | 35 | # Another section 36 | " 37 | `; 38 | 39 | exports[`readme command --readme-file 1`] = ` 40 | "# A title 41 | 42 | # API 43 | 44 | 45 | 46 | ### Table of Contents 47 | 48 | * [foo](#foo) 49 | * [Parameters](#parameters) 50 | * [bar](#bar) 51 | * [Parameters](#parameters-1) 52 | 53 | ## foo 54 | 55 | A function with documentation. 56 | 57 | ### Parameters 58 | 59 | * \`a\` {string} blah 60 | 61 | Returns **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global\\\\_Objects/Number)** answer 62 | 63 | ## bar 64 | 65 | A second function with docs 66 | 67 | ### Parameters 68 | 69 | * \`b\` 70 | 71 | # Another section 72 | " 73 | `; 74 | 75 | exports[`readme command updates README.md 1`] = ` 76 | "# A title 77 | 78 | # API 79 | 80 | 81 | 82 | ### Table of Contents 83 | 84 | * [foo](#foo) 85 | * [Parameters](#parameters) 86 | * [bar](#bar) 87 | * [Parameters](#parameters-1) 88 | 89 | ## foo 90 | 91 | A function with documentation. 92 | 93 | ### Parameters 94 | 95 | * \`a\` {string} blah 96 | 97 | Returns **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global\\\\_Objects/Number)** answer 98 | 99 | ## bar 100 | 101 | A second function with docs 102 | 103 | ### Parameters 104 | 105 | * \`b\` 106 | 107 | # Another section 108 | " 109 | `; 110 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/index.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`build 1`] = ` 4 | Array [ 5 | Object { 6 | "augments": Array [], 7 | "description": Object { 8 | "children": Array [ 9 | Object { 10 | "children": Array [ 11 | Object { 12 | "type": "text", 13 | "value": "hi", 14 | }, 15 | ], 16 | "type": "paragraph", 17 | }, 18 | ], 19 | "type": "root", 20 | }, 21 | "errors": Array [], 22 | "examples": Array [], 23 | "implements": Array [], 24 | "loc": SourceLocation { 25 | "end": Position { 26 | "column": 9, 27 | "index": 9, 28 | "line": 1, 29 | }, 30 | "filename": undefined, 31 | "identifierName": undefined, 32 | "start": Position { 33 | "column": 0, 34 | "index": 0, 35 | "line": 1, 36 | }, 37 | }, 38 | "members": Object { 39 | "events": Array [], 40 | "global": Array [], 41 | "inner": Array [], 42 | "instance": Array [], 43 | "static": Array [], 44 | }, 45 | "name": "name", 46 | "namespace": "name", 47 | "params": Array [], 48 | "path": Array [ 49 | Object { 50 | "kind": undefined, 51 | "name": "name", 52 | }, 53 | ], 54 | "properties": Array [], 55 | "returns": Array [], 56 | "sees": Array [], 57 | "tags": Array [], 58 | "throws": Array [], 59 | "todos": Array [], 60 | "yields": Array [], 61 | }, 62 | ] 63 | `; 64 | 65 | exports[`build 2`] = ` 66 | " 67 | 68 | ## name 69 | 70 | hi 71 | " 72 | `; 73 | 74 | exports[`build 3`] = ` 75 | "[ 76 | { 77 | \\"description\\": { 78 | \\"type\\": \\"root\\", 79 | \\"children\\": [ 80 | { 81 | \\"type\\": \\"paragraph\\", 82 | \\"children\\": [ 83 | { 84 | \\"type\\": \\"text\\", 85 | \\"value\\": \\"hi\\" 86 | } 87 | ] 88 | } 89 | ] 90 | }, 91 | \\"tags\\": [], 92 | \\"loc\\": { 93 | \\"start\\": { 94 | \\"line\\": 1, 95 | \\"column\\": 0, 96 | \\"index\\": 0 97 | }, 98 | \\"end\\": { 99 | \\"line\\": 1, 100 | \\"column\\": 9, 101 | \\"index\\": 9 102 | } 103 | }, 104 | \\"augments\\": [], 105 | \\"examples\\": [], 106 | \\"implements\\": [], 107 | \\"params\\": [], 108 | \\"properties\\": [], 109 | \\"returns\\": [], 110 | \\"sees\\": [], 111 | \\"throws\\": [], 112 | \\"todos\\": [], 113 | \\"yields\\": [], 114 | \\"name\\": \\"name\\", 115 | \\"members\\": { 116 | \\"global\\": [], 117 | \\"inner\\": [], 118 | \\"instance\\": [], 119 | \\"events\\": [], 120 | \\"static\\": [] 121 | }, 122 | \\"path\\": [ 123 | { 124 | \\"name\\": \\"name\\" 125 | } 126 | ], 127 | \\"namespace\\": \\"name\\" 128 | } 129 | ]" 130 | `; 131 | -------------------------------------------------------------------------------- /__tests__/config_fixture/config: -------------------------------------------------------------------------------- 1 | foo: bar -------------------------------------------------------------------------------- /__tests__/config_fixture/config.json: -------------------------------------------------------------------------------- 1 | { "foo": "bar" } 2 | -------------------------------------------------------------------------------- /__tests__/config_fixture/config.yaml: -------------------------------------------------------------------------------- 1 | foo: bar 2 | -------------------------------------------------------------------------------- /__tests__/config_fixture/config.yml: -------------------------------------------------------------------------------- 1 | foo: bar 2 | -------------------------------------------------------------------------------- /__tests__/config_fixture/config_comments.json: -------------------------------------------------------------------------------- 1 | // there is a comment here 2 | { "foo": "bar" } 3 | -------------------------------------------------------------------------------- /__tests__/config_fixture/config_file.yml: -------------------------------------------------------------------------------- 1 | toc: 2 | - name: snowflake 3 | file: ../fixture/snowflake.md 4 | -------------------------------------------------------------------------------- /__tests__/config_fixture/config_links.yml: -------------------------------------------------------------------------------- 1 | foo: >- 2 | hello [link](https://github.com/my/link) world 3 | -------------------------------------------------------------------------------- /__tests__/fixture/auto_lang_hljs/config.yml: -------------------------------------------------------------------------------- 1 | hljs: 2 | highlightAuto: true 3 | languages: 4 | - js 5 | - css 6 | - html 7 | -------------------------------------------------------------------------------- /__tests__/fixture/auto_lang_hljs/multilanguage.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function returns the number one. 3 | * @returns {Number} numberone 4 | * @example 5 | * var myFoo = new Foo('[data-foo]'); 6 | * myFoo.foo(42); 7 | * @example 8 | *

Data-Foo Element in the dom

9 | * @example 10 | * [data-foo] { 11 | * background-color: red; 12 | * } 13 | * @throws {Error} if you give it something 14 | * @throws {TypeError} if you give it something else 15 | * @augments Foo 16 | * @augments Bar 17 | */ 18 | module.exports = function() { 19 | // this returns 1 20 | return 1; 21 | }; 22 | -------------------------------------------------------------------------------- /__tests__/fixture/auto_lang_hljs/multilanguage.output.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## multilanguage.input 4 | 5 | **Extends Foo, Bar** 6 | 7 | This function returns the number one. 8 | 9 | **Examples** 10 | 11 | ```js 12 | var myFoo = new Foo('[data-foo]'); 13 | myFoo.foo(42); 14 | ``` 15 | 16 | ```html 17 |

Data-Foo Element in the dom

18 | ``` 19 | 20 | ```css 21 | [data-foo] { 22 | background-color: red; 23 | } 24 | ``` 25 | 26 | - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** if you give it something 27 | - Throws **[TypeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError)** if you give it something else 28 | 29 | Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** numberone 30 | -------------------------------------------------------------------------------- /__tests__/fixture/bad/syntax.input: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /__tests__/fixture/bad/syntax.output.json: -------------------------------------------------------------------------------- 1 | { 2 | "pos": 19, 3 | "loc": { 4 | "line": 2, 5 | "column": 0 6 | }, 7 | "_babel": true 8 | } -------------------------------------------------------------------------------- /__tests__/fixture/boolean-literal-type.input.js: -------------------------------------------------------------------------------- 1 | /** */ 2 | function f(t: true, f: false): [true, false] { 3 | return [t, f]; 4 | } 5 | -------------------------------------------------------------------------------- /__tests__/fixture/class.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is my class, a demo thing. 3 | * @class MyClass 4 | * @implements {MyInterface} 5 | * @property {number} howMany how many things it contains 6 | */ 7 | function MyClass() { 8 | this.howMany = 2; 9 | } 10 | 11 | /** 12 | * Get the number 42 13 | * @param {boolean} getIt whether to get the number 14 | * @returns {number} forty-two 15 | */ 16 | MyClass.prototype.getFoo = function(getIt) { 17 | return getIt ? 42 : 0; 18 | }; 19 | 20 | /** 21 | * Get undefined 22 | * @returns {undefined} does not return anything. 23 | */ 24 | MyClass.prototype.getUndefined = function() {}; 25 | -------------------------------------------------------------------------------- /__tests__/fixture/config-bad.yml: -------------------------------------------------------------------------------- 1 | toc: 2 | - notfound 3 | -------------------------------------------------------------------------------- /__tests__/fixture/config-malformed.json: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /__tests__/fixture/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "toc": ["bananas", "carrots", "apples"] 3 | } 4 | -------------------------------------------------------------------------------- /__tests__/fixture/custom_theme/index.js: -------------------------------------------------------------------------------- 1 | var File = require('vinyl'); 2 | 3 | /** 4 | * This is a theme only used by documentation to test custom theme 5 | * support. 6 | */ 7 | module.exports = function(comments, options, callback) { 8 | return Promise.resolve([ 9 | new File({ 10 | base: '/', 11 | path: '/index.html', 12 | contents: Buffer.from('Hello world') 13 | }) 14 | ]); 15 | }; 16 | -------------------------------------------------------------------------------- /__tests__/fixture/default-export-function.input.js: -------------------------------------------------------------------------------- 1 | /** i am foo */ 2 | export default function() { 3 | /** i am foo's son */ 4 | this.bar = () => {}; 5 | } 6 | -------------------------------------------------------------------------------- /__tests__/fixture/document-exported-bad/exports-z.js: -------------------------------------------------------------------------------- 1 | export var z = 1; 2 | -------------------------------------------------------------------------------- /__tests__/fixture/document-exported-bad/x.js: -------------------------------------------------------------------------------- 1 | export { y as x } from './exports-z.js'; 2 | -------------------------------------------------------------------------------- /__tests__/fixture/document-exported-export-default-object.input.js: -------------------------------------------------------------------------------- 1 | // Options: {"documentExported": true} 2 | 3 | export default { 4 | x: 42 5 | }; 6 | -------------------------------------------------------------------------------- /__tests__/fixture/document-exported-export-default-value.input.js: -------------------------------------------------------------------------------- 1 | // Options: {"documentExported": true} 2 | 3 | export default 42; 4 | -------------------------------------------------------------------------------- /__tests__/fixture/document-exported.input.js: -------------------------------------------------------------------------------- 1 | // Options: {"documentExported": true} 2 | 3 | export class Class { 4 | constructor(a: string) {} 5 | classMethod() {} 6 | get classGetter() {} 7 | set classSetter(v) {} 8 | static staticMethod() {} 9 | static get staticGetter() {} 10 | static set staticSetter(v) {} 11 | } 12 | 13 | export var object = { 14 | method() {}, 15 | get getter() {}, 16 | set setter(v) {}, 17 | prop: 42, 18 | func: function() {} 19 | }; 20 | 21 | /** Should not document this */ 22 | class NotExportedClass { 23 | /** Should not document this */ 24 | classMethod() {} 25 | /** Should not document this */ 26 | get classGetter() {} 27 | /** Should not document this */ 28 | set classSetter(v) {} 29 | /** Should not document this */ 30 | static staticMethod() {} 31 | /** Should not document this */ 32 | static get staticGetter() {} 33 | /** Should not document this */ 34 | static set staticSetter(v) {} 35 | } 36 | 37 | /** Should not document this */ 38 | var notExportedObject = { 39 | /** Should not document this */ 40 | method() {}, 41 | /** Should not document this */ 42 | get getter() {}, 43 | /** Should not document this */ 44 | set setter(v) {}, 45 | /** Should not document this */ 46 | prop: 42, 47 | /** Should not document this */ 48 | func: function() {} 49 | }; 50 | 51 | export { x, y3 as y4 } from './document-exported/x'; 52 | export z from './document-exported/z.js'; 53 | export y2Default from './document-exported/y.js'; 54 | 55 | function f1() {} 56 | function f2() {} 57 | 58 | export { f1, f2 as f3 }; 59 | 60 | export type T = number; 61 | type T2 = string; 62 | type T3 = string; 63 | 64 | export type { T2, T3 as T4 }; 65 | 66 | export type { T5 } from './document-exported/x.js'; 67 | 68 | export var f4 = function(x: X) {}; 69 | 70 | export { f5 }; 71 | 72 | export var o1 = { 73 | om1() {} 74 | }; 75 | 76 | /** f5 comment */ 77 | var f5 = function(y: Y) {}, 78 | o2 = { 79 | om2() {} 80 | }; 81 | export { o2 }; 82 | -------------------------------------------------------------------------------- /__tests__/fixture/document-exported/x.js: -------------------------------------------------------------------------------- 1 | export { y as x, y3 } from './y.js'; 2 | 3 | export type T5 = boolean; 4 | -------------------------------------------------------------------------------- /__tests__/fixture/document-exported/y.js: -------------------------------------------------------------------------------- 1 | export function y(yparam) {} 2 | 3 | function y2() {} 4 | 5 | export default y2; 6 | 7 | /** Description of y3 */ 8 | function y3(p: number): void {} 9 | 10 | export { y3 }; 11 | -------------------------------------------------------------------------------- /__tests__/fixture/document-exported/z.js: -------------------------------------------------------------------------------- 1 | export default class z { 2 | zMethod() {} 3 | } 4 | -------------------------------------------------------------------------------- /__tests__/fixture/es6-class-property.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is for issue 906. 3 | */ 4 | export class Issue906 { 5 | /** 6 | * This is a read-write property. 7 | * @type {boolean} 8 | */ 9 | get readWriteProp() { 10 | return this._rw; 11 | } 12 | 13 | set readWriteProp(value) { 14 | this._rw = value; 15 | } 16 | 17 | /** 18 | * This is a read-only property. 19 | * @type {string} 20 | * @readonly 21 | */ 22 | get readOnlyProp() { 23 | return 'foo'; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /__tests__/fixture/es6-class.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is my component. This is from issue #458 3 | */ 4 | class Foo extends React.Component {} 5 | 6 | /** 7 | * Does nothing. This is from issue #556 8 | * @param {string} str 9 | */ 10 | export class Bar { 11 | constructor(str) { 12 | /** 13 | * A useless property 14 | * @type {string} 15 | */ 16 | this.bar = ''; 17 | } 18 | } 19 | 20 | /** 21 | * This class has fully inferred constructor parameters. 22 | */ 23 | export class Baz { 24 | constructor(n: number, l: Array) {} 25 | } 26 | -------------------------------------------------------------------------------- /__tests__/fixture/es6-default2.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @public 3 | */ 4 | export default thisIsTheArgument => {}; 5 | -------------------------------------------------------------------------------- /__tests__/fixture/es6-ext.es6: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the default export frogs! 3 | */ 4 | export default 10; 5 | -------------------------------------------------------------------------------- /__tests__/fixture/es6-import.input.js: -------------------------------------------------------------------------------- 1 | import hasEx6 from './es6-ext'; 2 | import multiply from './simple.input.js'; 3 | import * as foo from 'some-other-module'; 4 | 5 | // Disable dynamic imports for now until 6 | // https://github.com/thgreasi/babel-plugin-system-import-transformer 7 | // can be updated to support babel 7. 8 | // import('./simple.input.js').then(() => {}); 9 | 10 | /** 11 | * This function returns the number one. 12 | * @returns {Number} numberone 13 | */ 14 | var multiplyTwice = a => a * multiply(a); 15 | 16 | export default multiplyTwice; 17 | -------------------------------------------------------------------------------- /__tests__/fixture/event.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mouse event 3 | * 4 | * @event Map#mousemove 5 | * @type {Object} 6 | * @property {Point} point the pixel location of the event 7 | * @property {Event} originalEvent the original DOM event 8 | */ 9 | -------------------------------------------------------------------------------- /__tests__/fixture/example-caption.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function returns the number one. 3 | * @returns {Number} numberone 4 | * @example demonstrates how to run foo 5 | * foo(1); 6 | */ 7 | function foo() { 8 | // this returns 1 9 | return 1; 10 | } 11 | -------------------------------------------------------------------------------- /__tests__/fixture/extension.jsx: -------------------------------------------------------------------------------- 1 | var required = require('./extension/extension-required'); 2 | -------------------------------------------------------------------------------- /__tests__/fixture/extension/extension-required.jsx: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /__tests__/fixture/extension/index.otherextension: -------------------------------------------------------------------------------- 1 | /** 2 | * apples 3 | */ 4 | function apples() {} 5 | 6 | var HelloMessage = React.createClass({ 7 | render: function() { 8 | return
Hello {this.props.name}
; 9 | } 10 | }); 11 | 12 | ReactDOM.render(, mountNode); 13 | -------------------------------------------------------------------------------- /__tests__/fixture/external.input.js: -------------------------------------------------------------------------------- 1 | require('external'); 2 | require('external2'); 3 | require('module-not-found'); 4 | 5 | /** 6 | * I am in `external.input.js`. 7 | */ 8 | function foo() { 9 | return 'bar'; 10 | } 11 | 12 | module.exports = foo; 13 | -------------------------------------------------------------------------------- /__tests__/fixture/factory.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * an area chart generator 3 | * @returns {area} chart 4 | */ 5 | var area = function() { 6 | /** 7 | * @class area 8 | */ 9 | var chart = function(selection) {}; 10 | 11 | /** 12 | * Sets the chart data. 13 | * @function 14 | */ 15 | chart.data = function(_) {}; 16 | 17 | return chart; 18 | }; 19 | -------------------------------------------------------------------------------- /__tests__/fixture/flow-exported-opaque-type.input.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** MyOpaqueType Description Here */ 4 | export opaque type MyOpaqueType: string = string -------------------------------------------------------------------------------- /__tests__/fixture/flow-optional-chaining.input.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** foo */ 4 | const foo: { prop1?: { prop2?: string } } = { prop1: { prop2: 'value' } }; 5 | /** value */ 6 | const value = foo?.prop1?.prop2; 7 | -------------------------------------------------------------------------------- /__tests__/fixture/flow-unnamed-params.input.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** x */ 4 | let x: T => string; 5 | 6 | /** x2 */ 7 | let x2: (a: T) => string; 8 | 9 | /** T */ 10 | type T = (string[]) => { num: number }; 11 | 12 | /** T2 */ 13 | type T2 = (a: string[]) => { num: number }; 14 | 15 | /** T3 */ 16 | type T3 = (a?: string) => { num: number }; 17 | -------------------------------------------------------------------------------- /__tests__/fixture/flow/comment-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /*:: 4 | type Foo = { 5 | foo: number, 6 | bar: boolean, 7 | baz: string 8 | }; 9 | */ 10 | 11 | class MyClass { 12 | /*:: prop: Foo; */ 13 | 14 | method(value /*: Foo */) /*: boolean */ { 15 | return value.bar; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /__tests__/fixture/html/documentation.yml: -------------------------------------------------------------------------------- 1 | toc: 2 | - name: Highlighted section 3 | description: | 4 | The public key is a base64 encoded string of a protobuf containing an RSA DER 5 | buffer. This uses a node buffer to pass the base64 encoded public key protobuf 6 | to the multihash for ID generation. 7 | 8 | ```js 9 | var PeerId = require('peer-id') 10 | 11 | PeerId.create({ bits: 1024 }, (err, id) => { 12 | console.log(JSON.stringify(id.toJSON(), null, 2) 13 | }) 14 | ``` 15 | 16 | ``` 17 | { 18 | "id": "Qma9T5YraSnpRDZqRR4krcSJabThc8nwZuJV3LercPHufi", 19 | "privKey": "CAAS4AQwggJcAgEAAoGBAMBgbIqyOL26oV3nGPBYrdpbv..", 20 | "pubKey": "CAASogEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMBgbIqyOL26oV3nGPBYrdpbvzCY..." 21 | } 22 | ``` 23 | -------------------------------------------------------------------------------- /__tests__/fixture/import.meta.input.js: -------------------------------------------------------------------------------- 1 | import x from "y"; 2 | 3 | 4 | console.log(import.meta.url); -------------------------------------------------------------------------------- /__tests__/fixture/infer-private.input.js: -------------------------------------------------------------------------------- 1 | // Options: {"inferPrivate": "^_"} 2 | 3 | /** 4 | * _p description 5 | */ 6 | function _p() {} 7 | 8 | /** C description */ 9 | class C { 10 | /** m description */ 11 | m() {} 12 | /** _p description */ 13 | _p() {} 14 | } 15 | -------------------------------------------------------------------------------- /__tests__/fixture/inheritance.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * With ES6, built-in types are extensible! 3 | */ 4 | class SpecialArray extends Array { 5 | additionalMethod() {} 6 | } 7 | 8 | /** @class Foo */ 9 | module.exports = class Foo extends Bar {}; 10 | -------------------------------------------------------------------------------- /__tests__/fixture/inline-link.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adds one to a number 3 | * @param {number} a the input 4 | * @returns {number} the output 5 | */ 6 | function addOne(a) { 7 | return a + 1; 8 | } 9 | 10 | /** 11 | * This function returns the number one. Internally, this uses 12 | * {@link addOne} to do the math. This demonstrates 13 | * {@link https://en.wikipedia.org/wiki/Addition Addition} 14 | * and {@link https://en.wikipedia.org/wiki/Addition} 15 | * 16 | * This link refers to nothing: {@link nothing} 17 | * 18 | * @param {number} a the input 19 | * @returns {number} numberone 20 | */ 21 | module.exports = function(a) { 22 | return addOne(a); 23 | }; 24 | -------------------------------------------------------------------------------- /__tests__/fixture/interface.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is my interface. 3 | */ 4 | interface Foo extends Bar, Baz { 5 | /** This is prop 1 */ 6 | prop1: number, 7 | /** This is prop 2 */ 8 | prop2: string 9 | } 10 | -------------------------------------------------------------------------------- /__tests__/fixture/internal.input.js: -------------------------------------------------------------------------------- 1 | var otherModule = require('./external.input.js'); 2 | module.exports = otherModule; 3 | -------------------------------------------------------------------------------- /__tests__/fixture/lends.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A neat layout view 3 | * @class TheClass 4 | * @augments Augmented 5 | */ 6 | export default TheClass( 7 | /** @lends TheClass.prototype */ 8 | { 9 | /** My field */ 10 | 'my-field': true, 11 | /** 12 | * My neat function 13 | * @param {string} word your word 14 | * @returns {string} your word but one better 15 | */ 16 | foo: function(word) { 17 | return word + 1; 18 | }, 19 | /** 20 | * My neat function 21 | * @param {string} word your word 22 | * @returns {string} your word but one better 23 | */ 24 | bar(word) { 25 | return word + 1; 26 | } 27 | } 28 | ); 29 | -------------------------------------------------------------------------------- /__tests__/fixture/lint/lint.input.dependency.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {String} foo bar 3 | * @returns {object} bad object return type 4 | * @type {Array} bad object type 5 | * @memberof notfound 6 | */ 7 | 8 | /** 9 | * @param {String} baz bar 10 | * @property {String} bad property 11 | * @private 12 | */ 13 | 14 | /** 15 | * @param {number} c explicit but not found 16 | */ 17 | function add(a, b) {} 18 | 19 | module.exports.add = add; 20 | -------------------------------------------------------------------------------- /__tests__/fixture/lint/lint.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {String} foo bar 3 | * @param {Array} foo bar 4 | * @param {Array} foo bar 5 | * @param {Array|Number} foo bar 6 | * @param {boolean} 7 | * @returns {object} bad object return type 8 | * @type {Array} bad object type 9 | * @memberof notfound 10 | */ 11 | 12 | /** 13 | * @param {String} baz bar 14 | * @property {String} bad property 15 | * @private 16 | */ 17 | 18 | /** 19 | * @param {number} c explicit but not found 20 | */ 21 | function add(a, b) {} 22 | -------------------------------------------------------------------------------- /__tests__/fixture/lint/lint.input.shallow.js: -------------------------------------------------------------------------------- 1 | var dep = require('./lint.input.dependency'); 2 | 3 | /** 4 | * @param {string} a 5 | * @param {boolean} b 6 | */ 7 | function add(a, b) {} 8 | -------------------------------------------------------------------------------- /__tests__/fixture/lint/lint.output: -------------------------------------------------------------------------------- 1 | 2:1 warning An explicit parameter named foo was specified but didn't match inferred information a, b 2 | 3:1 warning type Number found, number is standard 3 | 3:1 warning An explicit parameter named foo was specified but didn't match inferred information a, b 4 | 4:1 warning type Number found, number is standard 5 | 4:1 warning An explicit parameter named foo was specified but didn't match inferred information a, b 6 | 5:1 warning type Number found, number is standard 7 | 5:1 warning An explicit parameter named foo was specified but didn't match inferred information a, b 8 | 6:1 warning An explicit parameter named boolean was specified but didn't match inferred information a, b 9 | 7:1 warning type object found, Object is standard 10 | 8:1 warning type object found, Object is standard 11 | 9:1 warning @memberof reference to notfound not found 12 | 13:1 warning type String found, string is standard 13 | 13:1 warning An explicit parameter named baz was specified but didn't match inferred information a, b 14 | 14:1 warning type String found, string is standard 15 | 19:1 warning An explicit parameter named c was specified but didn't match inferred information a, b 16 | 17 | ⚠ 16 warnings 18 | -------------------------------------------------------------------------------- /__tests__/fixture/literal_types.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {'a' | "b" | '' | 0 | -42 | 3.14} x 3 | */ 4 | function f(x) {} 5 | 6 | /** */ 7 | function g(x: 'a' | 'b' | '' | 0 | -42 | 3.14) {} 8 | -------------------------------------------------------------------------------- /__tests__/fixture/memberedclass.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is my class, a demo thing. 3 | * 4 | * @class MyClass 5 | * @memberof com.Test 6 | */ 7 | com.Test.MyClass = class { 8 | constructor() { 9 | this.howMany = 2; 10 | } 11 | 12 | /** 13 | * Get the number 42 14 | * 15 | * @param {boolean} getIt whether to get the number 16 | * @returns {number} forty-two 17 | */ 18 | getFoo(getIt) { 19 | return getIt ? 42 : 0; 20 | } 21 | 22 | /** 23 | * Get undefined 24 | * 25 | * @returns {undefined} does not return anything. 26 | */ 27 | static getUndefined() {} 28 | }; 29 | -------------------------------------------------------------------------------- /__tests__/fixture/merge-infered-type.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Add five to `x`. 3 | * 4 | * @param x The number to add five to. 5 | * @returns x plus five. 6 | */ 7 | function addFive(x: number): number { 8 | return x + 5; 9 | } 10 | -------------------------------------------------------------------------------- /__tests__/fixture/meta.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This description is ignored because the method has a tagged description. 3 | * @returns {number} numberone 4 | * @description This function returns the number one. 5 | * @see [markdown link](http://foo.com/) 6 | * @version 1.0.0 7 | * @since 2.0.0 8 | * @copyright Tom MacWright 9 | * @license BSD 10 | */ 11 | module.exports = function() { 12 | // this returns 1 13 | return 1; 14 | }; 15 | -------------------------------------------------------------------------------- /__tests__/fixture/multisignature.input.js: -------------------------------------------------------------------------------- 1 | var theTime; 2 | 3 | /** 4 | * Get the time 5 | * @returns {Date} the current date 6 | */ 7 | 8 | /** 9 | * Set the time 10 | * @param {Date} time the current time 11 | * @returns {undefined} nothing 12 | */ 13 | function getTheTime(time) { 14 | if (arguments.length === 0) { 15 | return new Date(); 16 | } else { 17 | theTime = time; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /__tests__/fixture/nearby_params.input.js: -------------------------------------------------------------------------------- 1 | /** Attempt to establish a cookie-based session in exchange for credentials. 2 | * @function 3 | * @name sessions.create 4 | * @param {object} credentials 5 | * @param {string} credentials.name Login username. Also accepted as `username` or `email`. 6 | * @param {string} credentials.password Login password 7 | * @param {function} [callback] Gets passed `(err, { success:Boolean })`. 8 | * @returns {Promise} promise, to be resolved on success or rejected on failure 9 | */ 10 | sessions.addMethod('create', 'POST / form', { 11 | // normalize request body params 12 | before({ body }) {} 13 | }); 14 | -------------------------------------------------------------------------------- /__tests__/fixture/nest_events.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Klass description 3 | * 4 | * @class Klass 5 | */ 6 | 7 | 8 | /** 9 | * Klass event 10 | * @event bar 11 | * @memberof Klass 12 | */ 13 | bar(); -------------------------------------------------------------------------------- /__tests__/fixture/nest_params.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Object[]} employees - The employees who are responsible for the project. 3 | * @param {string} employees[].name - The name of an employee. 4 | * @param {string} employees[].department - The employee's department. 5 | * @param {string} [type=minion] - The employee's type. 6 | */ 7 | function foo(employees, type) {} 8 | 9 | /** 10 | * @name foo 11 | * @param {Object} options some options 12 | * @param {number} options.much how much 13 | * @param {number} bar something else 14 | * @property {Object} theTime the current time 15 | * @property {number} theTime.hours 16 | * @property {number} theTime.minutes 17 | * @property {number} theTime.seconds 18 | * @returns {Object} foo something else 19 | */ 20 | -------------------------------------------------------------------------------- /__tests__/fixture/newline-in-description.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A function. 3 | * @param {Number} a - The input to the function. 4 | * I should be able to continue the description on a new line, and have it 5 | * still work in the markdown table. 6 | */ 7 | -------------------------------------------------------------------------------- /__tests__/fixture/no-name.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set the time 3 | * @param {number} bar 4 | */ 5 | -------------------------------------------------------------------------------- /__tests__/fixture/node_modules/external/index.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /__tests__/fixture/node_modules/external/lib/main.js: -------------------------------------------------------------------------------- 1 | var otherDep = require('external2'); 2 | 3 | /** 4 | * This function returns the number one. 5 | * @returns {Number} numberone 6 | */ 7 | module.exports = function() { 8 | // this returns 1 9 | return otherDep() - 1; 10 | }; 11 | -------------------------------------------------------------------------------- /__tests__/fixture/node_modules/external/node_modules/external2/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function returns the number one. 3 | * @return {Number} numberone 4 | */ 5 | module.exports = function() { 6 | // this returns 1 7 | return 1; 8 | }; 9 | -------------------------------------------------------------------------------- /__tests__/fixture/node_modules/external/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "lib/main.js" 3 | } 4 | -------------------------------------------------------------------------------- /__tests__/fixture/node_modules/external2/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function returns the number one. 3 | * @returns {Number} numberone 4 | */ 5 | module.exports = function() { 6 | // this returns 1 7 | return 1; 8 | }; 9 | -------------------------------------------------------------------------------- /__tests__/fixture/optional-record-field-type.input.js: -------------------------------------------------------------------------------- 1 | /** */ 2 | type Record = { 3 | opt?: number, 4 | req: string 5 | }; 6 | -------------------------------------------------------------------------------- /__tests__/fixture/params.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function returns the number one. 3 | * @param {number} b the second param 4 | */ 5 | function addThem(a, b, c, { d, e, f }) { 6 | return a + b + c + d + e + f; 7 | } 8 | 9 | /** 10 | * This method has partially inferred params 11 | * @param {Object} options 12 | * @param {String} options.fishes number of kinds of fish 13 | */ 14 | function fishesAndFoxes({ fishes, foxes }) { 15 | return fishes + foxes; 16 | } 17 | 18 | /** 19 | * This method has a type in the description and a default in the code 20 | * @param {number} x 21 | */ 22 | function withDefault(x = 2) { 23 | return x; 24 | } 25 | 26 | /** 27 | * This is foo's documentation 28 | */ 29 | class Foo { 30 | /** 31 | * The method 32 | * @param {number} x Param to method 33 | */ 34 | method(x) {} 35 | } 36 | 37 | /** 38 | * Traditional object 39 | */ 40 | var TraditionalObject = { 41 | /** 42 | * This method should acquire the param x 43 | */ 44 | traditionalMethod: function(x) { 45 | return x; 46 | } 47 | }; 48 | 49 | /** 50 | * Represents an IPv6 address 51 | * 52 | * This tests our support of optional parameters 53 | * @class Address6 54 | * @param {string} address - An IPv6 address string 55 | * @param {number} [groups=8] - How many octets to parse 56 | * @param {?number} third - A third argument 57 | * @param {Array} [foo=[1]] to properly be parsed 58 | * @example 59 | * var address = new Address6('2001::/32'); 60 | */ 61 | function Address6() {} 62 | 63 | /** 64 | * Create a GeoJSON data source instance given an options object 65 | * 66 | * This tests our support of nested parameters 67 | * @class GeoJSONSource 68 | * @param {Object} [options] optional options 69 | * @param {Object|string} options.data A GeoJSON data object or URL to it. 70 | * The latter is preferable in case of large GeoJSON files. 71 | * @param {number} [options.maxzoom=14] Maximum zoom to preserve detail at. 72 | * @param {number} [options.buffer] Tile buffer on each side. 73 | * @param {number} [options.tolerance] Simplification tolerance (higher means simpler). 74 | */ 75 | function GeoJSONSource(options) { 76 | this.options = options; 77 | } 78 | 79 | /** 80 | * This tests our support for parameters with explicit types but with default 81 | * values specified in code. 82 | * 83 | * @param {number} x an argument 84 | * 85 | * @returns {number} some 86 | */ 87 | export const myfunc = (x = 123) => x; 88 | 89 | /** 90 | * This tests our support of JSDoc param tags without type information, 91 | * or any type information we could infer from annotations. 92 | * 93 | * @param address - An IPv6 address string 94 | */ 95 | function foo(address) { 96 | return address; 97 | } 98 | 99 | /** 100 | * This tests our support for iterator rest inside an 101 | * iterator destructure (RestElement) 102 | * 103 | * @param {Array} input 104 | * @param {any} input.0 head of iterator 105 | * @param {...any} input.xs body of iterator 106 | * 107 | * @returns {any[]} rotated such that the last element was the first 108 | */ 109 | export function rotate([x, ...xs]) { 110 | return [...xs, x]; 111 | } 112 | -------------------------------------------------------------------------------- /__tests__/fixture/react-jsx.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * apples 3 | */ 4 | function apples() {} 5 | 6 | var HelloMessage = React.createClass({ 7 | render: function() { 8 | return
Hello {this.props.name}
; 9 | } 10 | }); 11 | 12 | ReactDOM.render(, mountNode); 13 | -------------------------------------------------------------------------------- /__tests__/fixture/readme/README.input.md: -------------------------------------------------------------------------------- 1 | # A title 2 | 3 | # API 4 | 5 | # Another section 6 | -------------------------------------------------------------------------------- /__tests__/fixture/readme/README.output.md: -------------------------------------------------------------------------------- 1 | # A title 2 | 3 | # API 4 | 5 | 6 | 7 | ### Table of Contents 8 | 9 | - [foo](#foo) 10 | - [bar](#bar) 11 | 12 | ## foo 13 | 14 | A function with documentation. 15 | 16 | **Parameters** 17 | 18 | - `a` {string} blah 19 | 20 | Returns **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** answer 21 | 22 | ## bar 23 | 24 | A second function with docs 25 | 26 | **Parameters** 27 | 28 | - `b` 29 | 30 | # Another section 31 | -------------------------------------------------------------------------------- /__tests__/fixture/readme/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A function with documentation. 3 | * @param a {string} blah 4 | * @return {number} answer 5 | */ 6 | function foo(a) {} 7 | 8 | /** 9 | * A second function with docs 10 | */ 11 | function bar(b) {} 12 | -------------------------------------------------------------------------------- /__tests__/fixture/require-json-no-extension.input.js: -------------------------------------------------------------------------------- 1 | var data = require('./require-json'); 2 | -------------------------------------------------------------------------------- /__tests__/fixture/require-json.input.js: -------------------------------------------------------------------------------- 1 | var data = require('./require-json.json'); 2 | -------------------------------------------------------------------------------- /__tests__/fixture/require-json.json: -------------------------------------------------------------------------------- 1 | {"foo":"bar"} 2 | -------------------------------------------------------------------------------- /__tests__/fixture/resolve/index.js: -------------------------------------------------------------------------------- 1 | const node = require('./node'); 2 | 3 | /** 4 | * test 5 | * @returns { string } 6 | * 7 | */ 8 | module.exports = function() { 9 | return 'a'; 10 | }; 11 | -------------------------------------------------------------------------------- /__tests__/fixture/resolve/node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * test 3 | * @returns { string } 4 | * 5 | */ 6 | module.exports = function() { 7 | return 'e'; 8 | }; 9 | -------------------------------------------------------------------------------- /__tests__/fixture/resolve/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resolve", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "browser": { 10 | "./node.js": false 11 | }, 12 | "author": "", 13 | "license": "ISC" 14 | } 15 | -------------------------------------------------------------------------------- /__tests__/fixture/sections.config.yml: -------------------------------------------------------------------------------- 1 | toc: 2 | - name: Alpha 3 | children: 4 | - third 5 | - first 6 | - name: Bravo 7 | description: Contains a subsection! 8 | children: 9 | - name: Charlie 10 | description: Second is in here 11 | children: 12 | - second 13 | -------------------------------------------------------------------------------- /__tests__/fixture/sections.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function is first 3 | */ 4 | function first() {} 5 | 6 | /** */ 7 | class AClass { 8 | /** 9 | * forgot a memberof here... sure hope that doesn't crash anything! 10 | * @method first 11 | */ 12 | first(x, y) {} 13 | 14 | /** 15 | * shares a name with a top level item referenced in the TOC... sure hope 16 | * that doesn't crash anything! 17 | */ 18 | second() {} 19 | } 20 | 21 | /** 22 | * This class has some members 23 | */ 24 | function second() {} 25 | 26 | /** 27 | * second::foo 28 | */ 29 | second.prototype.foo = function(pork) {}; 30 | 31 | /** 32 | * second::bar 33 | */ 34 | second.prototype.bar = function(beans, rice) {}; 35 | 36 | /** 37 | * This function is third 38 | */ 39 | function third() {} 40 | -------------------------------------------------------------------------------- /__tests__/fixture/simple-callback.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This takes a number and a callback and calls the callback with the number 3 | * plus 3. 4 | * 5 | * @param {Number} n - The number. 6 | * @param {simpleCallback} cb - The callback. 7 | */ 8 | function takesSimpleCallback(n, cb) { 9 | cb(null, n + 3); 10 | } 11 | 12 | /** 13 | * This callback takes an error and a number. 14 | * 15 | * @callback simpleCallback 16 | * @param {?Error} err - The error. 17 | * @param {Number} n - The number. 18 | */ 19 | -------------------------------------------------------------------------------- /__tests__/fixture/simple-hashbang.input.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * This function returns the number one. 5 | * @returns {Number} numberone 6 | */ 7 | module.exports = function() { 8 | // this returns 1 9 | return 1; 10 | }; 11 | -------------------------------------------------------------------------------- /__tests__/fixture/simple-private.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function returns the number plus two. 3 | * 4 | * @param {Number} a the number 5 | * @returns {Number} numbertwo 6 | * @private 7 | */ 8 | function returnTwo(a) { 9 | // this returns a + 2 10 | return a + 2; 11 | } 12 | -------------------------------------------------------------------------------- /__tests__/fixture/simple-singlestar.input.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This function returns the number one. 3 | * @returns {Number} numberone 4 | */ 5 | module.exports = function() { 6 | // this returns 1 7 | return 1; 8 | }; 9 | -------------------------------------------------------------------------------- /__tests__/fixture/simple-triplestar.input.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * This function returns the number one. 3 | * @returns {Number} numberone 4 | */ 5 | module.exports = function() { 6 | // this returns 1 7 | return 1; 8 | }; 9 | -------------------------------------------------------------------------------- /__tests__/fixture/simple-two.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function returns the number plus two. 3 | * 4 | * @param {Number} a the number 5 | * @returns {Number} numbertwo 6 | * @example 7 | * var result = returnTwo(4); 8 | * // result is 6 9 | */ 10 | function returnTwo(a) { 11 | // this returns a + 2 12 | return a + 2; 13 | } 14 | -------------------------------------------------------------------------------- /__tests__/fixture/simple.config.yml: -------------------------------------------------------------------------------- 1 | toc: 2 | - MyClass 3 | - name: Hello 4 | description: World 5 | -------------------------------------------------------------------------------- /__tests__/fixture/simple.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function returns the number one. 3 | * @returns {number} numberone 4 | */ 5 | module.exports = function() { 6 | // this returns 1 7 | return 1; 8 | }; 9 | -------------------------------------------------------------------------------- /__tests__/fixture/snowflake.md: -------------------------------------------------------------------------------- 1 | # The Snowflake 2 | -------------------------------------------------------------------------------- /__tests__/fixture/sort-order-alpha.input.js: -------------------------------------------------------------------------------- 1 | // Options: {"sortOrder": ["alpha"]} 2 | 3 | /** */ 4 | function b() {} 5 | 6 | /** */ 7 | function a() {} 8 | 9 | /** */ 10 | class C { 11 | /** */ 12 | b() {} 13 | /** */ 14 | B() {} 15 | /** */ 16 | a() {} 17 | /** */ 18 | A() {} 19 | } 20 | 21 | /** */ 22 | class D { 23 | /** */ 24 | b() {} 25 | /** */ 26 | B() {} 27 | /** */ 28 | a() {} 29 | /** */ 30 | A() {} 31 | } 32 | -------------------------------------------------------------------------------- /__tests__/fixture/sorting/input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Apples are red 3 | */ 4 | var apples = function() { 5 | return 'red'; 6 | }; 7 | 8 | /** 9 | * Bananas are yellow 10 | */ 11 | var bananas = function() { 12 | return 'yellow'; 13 | }; 14 | 15 | /** 16 | * Carrots are awesome 17 | */ 18 | var carrots = function() { 19 | return 'awesome'; 20 | }; 21 | -------------------------------------------------------------------------------- /__tests__/fixture/sorting/output-bad.txt: -------------------------------------------------------------------------------- 1 | Table of contents defined sorting of notfound but no documentation with that namepath was found 2 | -------------------------------------------------------------------------------- /__tests__/fixture/string-literal-key.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @alias MyContainerObject 3 | */ 4 | const obj = { 5 | /** 6 | * The foo property 7 | */ 8 | foo: { 9 | bar: 0 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /__tests__/fixture/this-class.input.js: -------------------------------------------------------------------------------- 1 | /** @module bookshelf */ 2 | 3 | /** @class */ 4 | var Book = function(title) { 5 | /** The title of the book. */ 6 | this.title = title; 7 | }; 8 | 9 | /** @class */ 10 | this.BookShelf = function(title) { 11 | /** The title of the bookshelf. */ 12 | this.title = title; 13 | }; 14 | -------------------------------------------------------------------------------- /__tests__/fixture/type_application.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents an IPv6 address 3 | * @class Address6 4 | * @param {Array} address - An IPv6 address string 5 | */ 6 | -------------------------------------------------------------------------------- /__tests__/fixture/var-function-param-return.input.js: -------------------------------------------------------------------------------- 1 | /** */ 2 | var f = function(x: number): boolean {}; 3 | -------------------------------------------------------------------------------- /__tests__/fixture/vue-no-script.input.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /__tests__/fixture/vue.input.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /__tests__/index.js: -------------------------------------------------------------------------------- 1 | import * as documentation from '../src/index.js'; 2 | import os from 'os'; 3 | import path from 'path'; 4 | import fs from 'fs'; 5 | 6 | function inputs(contents) { 7 | const dirEntry = os.tmpdir(); 8 | const paths = {}; 9 | for (const filename in contents) { 10 | paths[filename] = path.join(dirEntry, '/', filename); 11 | fs.writeFileSync(paths[filename], contents[filename]); 12 | } 13 | return { 14 | paths 15 | }; 16 | } 17 | 18 | function cleanup(comments) { 19 | comments.forEach(c => { 20 | delete c.context; 21 | }); 22 | } 23 | 24 | test('lint', async function () { 25 | const { paths } = inputs({ 26 | 'index.js': '/** hi */var name = 1;' 27 | }); 28 | 29 | const data = await documentation.lint([paths['index.js']], {}); 30 | expect(data).toEqual(''); 31 | }); 32 | 33 | test('build', async function () { 34 | const { paths } = inputs({ 35 | 'index.js': '/** hi */var name = 1;' 36 | }); 37 | 38 | const data = await documentation.build([paths['index.js']], {}); 39 | cleanup(data); 40 | expect(data).toMatchSnapshot(); 41 | 42 | const md = await documentation.formats.md(data); 43 | expect(md).toMatchSnapshot(); 44 | 45 | const json = await documentation.formats.json(data, {}); 46 | expect(json).toMatchSnapshot(); 47 | }); 48 | 49 | test('expandInputs', async function () { 50 | const { paths } = inputs({ 51 | 'index.js': '/** hi */var name = 1;' 52 | }); 53 | 54 | { 55 | const data = await documentation.expandInputs([paths['index.js']], { 56 | parseExtension: ['js'] 57 | }); 58 | expect(data.length).toEqual(1); 59 | } 60 | 61 | { 62 | const data = await documentation.expandInputs([paths['index.js']], { 63 | parseExtension: ['js'], 64 | shallow: true 65 | }); 66 | expect(data.length).toEqual(1); 67 | } 68 | }); 69 | -------------------------------------------------------------------------------- /__tests__/lib/filter_access.js: -------------------------------------------------------------------------------- 1 | import filterAccess from '../../src/filter_access.js'; 2 | 3 | test('filterAccess ignore', function () { 4 | expect( 5 | filterAccess( 6 | ['public', 'protected', 'undefined'], 7 | [ 8 | { 9 | access: 'public', 10 | ignore: true 11 | } 12 | ] 13 | ) 14 | ).toEqual([]); 15 | }); 16 | 17 | test('filterAccess public, protected, undefined, no private', function () { 18 | expect( 19 | filterAccess( 20 | ['public', 'protected', 'undefined'], 21 | [ 22 | { 23 | access: 'public' 24 | }, 25 | { 26 | access: 'protected' 27 | }, 28 | { 29 | foo: 2 30 | }, 31 | { 32 | access: 'private' 33 | } 34 | ] 35 | ) 36 | ).toEqual([ 37 | { 38 | access: 'public' 39 | }, 40 | { 41 | access: 'protected' 42 | }, 43 | { 44 | foo: 2 45 | } 46 | ]); 47 | }); 48 | 49 | test('filterAccess explicit public', function () { 50 | expect( 51 | filterAccess( 52 | ['public'], 53 | [ 54 | { access: 'public' }, 55 | { access: 'protected' }, 56 | { foo: 2 }, 57 | { access: 'private' } 58 | ] 59 | ) 60 | ).toEqual([ 61 | { 62 | access: 'public' 63 | } 64 | ]); 65 | }); 66 | 67 | test('filterAccess override', function () { 68 | expect( 69 | filterAccess( 70 | ['private'], 71 | [ 72 | { 73 | access: 'private' 74 | } 75 | ] 76 | ) 77 | ).toEqual([ 78 | { 79 | access: 'private' 80 | } 81 | ]); 82 | }); 83 | 84 | test('filterAccess nesting', function () { 85 | expect( 86 | filterAccess( 87 | ['public', 'protected', 'undefined'], 88 | [ 89 | { 90 | access: 'public', 91 | members: { 92 | static: [ 93 | { 94 | access: 'public' 95 | }, 96 | { 97 | access: 'private' 98 | } 99 | ] 100 | } 101 | }, 102 | { 103 | access: 'private', 104 | members: { 105 | static: [ 106 | { 107 | access: 'public' 108 | }, 109 | { 110 | access: 'private' 111 | } 112 | ] 113 | } 114 | } 115 | ] 116 | ) 117 | ).toEqual([ 118 | { 119 | access: 'public', 120 | members: { 121 | static: [ 122 | { 123 | access: 'public' 124 | } 125 | ] 126 | } 127 | } 128 | ]); 129 | }); 130 | -------------------------------------------------------------------------------- /__tests__/lib/git/find_git.js: -------------------------------------------------------------------------------- 1 | import mock from 'mock-fs'; 2 | import { mockRepo } from '../../utils.js'; 3 | import path from 'path'; 4 | import findGit from '../../../src/git/find_git.js'; 5 | import { fileURLToPath } from 'url'; 6 | 7 | const __filename = fileURLToPath(import.meta.url); 8 | const __dirname = path.dirname(__filename); 9 | 10 | test('findGit', function () { 11 | mock(mockRepo.master); 12 | const root = 13 | path.parse(__dirname).root + path.join('my', 'repository', 'path'); 14 | const masterPaths = findGit(path.join(root, 'index.js')); 15 | mock.restore(); 16 | 17 | expect(masterPaths).toEqual({ 18 | git: path.join(root, '.git'), 19 | root 20 | }); 21 | 22 | mock(mockRepo.submodule); 23 | const submoduleRoot = path.join(root, '..', 'my.submodule'); 24 | const submodulePaths = findGit(path.join(submoduleRoot, 'index.js')); 25 | mock.restore(); 26 | 27 | expect(submodulePaths).toEqual({ 28 | git: path.join( 29 | path.dirname(submoduleRoot), 30 | '.git', 31 | 'modules', 32 | 'my.submodule' 33 | ), 34 | root: submoduleRoot 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /__tests__/lib/git/url_prefix.js: -------------------------------------------------------------------------------- 1 | import mock from 'mock-fs'; 2 | import { mockRepo } from '../../utils.js'; 3 | import { 4 | getGithubURLPrefix, 5 | parsePackedRefs 6 | } from '../../../src/git/url_prefix.js'; 7 | 8 | test('getGithubURLPrefix', function () { 9 | mock(mockRepo.master); 10 | const masterUrl = getGithubURLPrefix({ 11 | git: '/my/repository/path/.git', 12 | root: '/my/repository/path' 13 | }); 14 | mock.restore(); 15 | 16 | expect(masterUrl).toBe('https://github.com/foo/bar/blob/this_is_the_sha/'); 17 | 18 | mock(mockRepo.detached); 19 | const detachedUrl = getGithubURLPrefix({ 20 | git: '/my/repository/path/.git', 21 | root: '/my/repository/path' 22 | }); 23 | mock.restore(); 24 | 25 | expect(detachedUrl).toBe( 26 | 'https://github.com/foo/bar/blob/e4cb2ffe677571d0503e659e4e64e01f45639c62/' 27 | ); 28 | 29 | mock(mockRepo.submodule); 30 | const submoduleUrl = getGithubURLPrefix({ 31 | git: '/my/repository/.git/modules/my.submodule', 32 | root: '/my/repository/my.submodule' 33 | }); 34 | mock.restore(); 35 | 36 | expect(submoduleUrl).toBe('https://github.com/foo/bar/blob/this_is_the_sha/'); 37 | }); 38 | 39 | test('parsePackedRefs', function () { 40 | const input = 41 | '# pack-refs with: peeled fully-peeled\n' + 42 | '4acd658617928bd17ae7364ef2512630d97c007a refs/heads/babel-6\n' + 43 | '11826ad98c6c08d00f4af77f64d3e2687e0f7dba refs/remotes/origin/flow-types'; 44 | expect(parsePackedRefs(input, 'refs/heads/babel-6')).toBe( 45 | '4acd658617928bd17ae7364ef2512630d97c007a' 46 | ); 47 | }); 48 | -------------------------------------------------------------------------------- /__tests__/lib/github.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: 0 */ 2 | import mock from 'mock-fs'; 3 | import path from 'path'; 4 | import { mockRepo } from '../utils.js'; 5 | import parse from '../../src/parsers/javascript.js'; 6 | import github from '../../src/github.js'; 7 | import { fileURLToPath } from 'url'; 8 | 9 | const __filename = fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(__filename); 11 | 12 | // mock-fs is causing some unusual side effects with jest-resolve 13 | // not being able to resolve modules so we've disabled these tests 14 | // for now. 15 | 16 | function toComment(fn, filename) { 17 | return parse( 18 | { 19 | file: filename, 20 | source: fn instanceof Function ? '(' + fn.toString() + ')' : fn 21 | }, 22 | {} 23 | ).map(github); 24 | } 25 | 26 | const root = path.parse(__dirname).root; 27 | 28 | function evaluate(fn) { 29 | return toComment( 30 | fn, 31 | root + path.join('my', 'repository', 'path', 'index.js') 32 | ); 33 | } 34 | 35 | afterEach(function () { 36 | mock.restore(); 37 | }); 38 | 39 | test.skip('github', function () { 40 | mock(mockRepo.master); 41 | 42 | expect( 43 | evaluate(function () { 44 | /** 45 | * get one 46 | * @returns {number} one 47 | */ 48 | function getOne() { 49 | return 1; 50 | } 51 | })[0].context.github 52 | ).toEqual({ 53 | path: 'index.js', 54 | url: 'https://github.com/foo/bar/blob/this_is_the_sha/index.js#L6-L8' 55 | }); 56 | }); 57 | 58 | test.skip('malformed repository', function () { 59 | mock(mockRepo.malformed); 60 | 61 | expect( 62 | evaluate(function () { 63 | /** 64 | * get one 65 | * @returns {number} one 66 | */ 67 | function getOne() { 68 | return 1; 69 | } 70 | })[0].context.github 71 | ).toBe(undefined); 72 | }); 73 | 74 | test.skip('enterprise repository', function () { 75 | mock(mockRepo.enterprise); 76 | 77 | expect( 78 | evaluate(function () { 79 | /** 80 | * get one 81 | * @returns {number} one 82 | */ 83 | function getOne() { 84 | return 1; 85 | } 86 | })[0].context.github 87 | ).toEqual({ 88 | path: 'index.js', 89 | url: 'https://github.enterprise.com/foo/bar/blob/this_is_the_sha/index.js#L6-L8' 90 | }); 91 | }); 92 | 93 | test.skip('typedef', function () { 94 | mock(mockRepo.master); 95 | 96 | expect( 97 | evaluate(function () { 98 | /** 99 | * A number, or a string containing a number. 100 | * @typedef {(number|string)} NumberLike 101 | */ 102 | 103 | /** 104 | * get one 105 | * @returns {number} one 106 | */ 107 | function getOne() { 108 | return 1; 109 | } 110 | })[0].context.github 111 | ).toEqual({ 112 | path: 'index.js', 113 | url: 'https://github.com/foo/bar/blob/this_is_the_sha/index.js#L2-L5' 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /__tests__/lib/infer/access.js: -------------------------------------------------------------------------------- 1 | import parse from '../../../src/parsers/javascript.js'; 2 | import inferName from '../../../src/infer/name.js'; 3 | import inferAccess from '../../../src/infer/access.js'; 4 | 5 | function toComment(fn, filename) { 6 | return parse( 7 | { 8 | file: filename, 9 | source: fn instanceof Function ? '(' + fn.toString() + ')' : fn 10 | }, 11 | {} 12 | )[0]; 13 | } 14 | 15 | function evaluate(fn, re, filename) { 16 | return inferAccess(re)(inferName(toComment(fn, filename))); 17 | } 18 | 19 | test('inferAccess', function () { 20 | expect( 21 | evaluate(function () { 22 | /** Test */ 23 | function _name() {} 24 | }, '^_').access 25 | ).toBe('private'); 26 | 27 | expect( 28 | evaluate(function () { 29 | /** @private */ 30 | function name() {} 31 | }, '^_').access 32 | ).toBe('private'); 33 | 34 | expect( 35 | evaluate(function () { 36 | /** @public */ 37 | function _name() {} 38 | }, '^_').access 39 | ).toBe('public'); 40 | 41 | expect( 42 | evaluate(function () { 43 | /** Test */ 44 | function name_() {} 45 | }, '_$').access 46 | ).toBe('private'); 47 | 48 | expect( 49 | evaluate( 50 | ` 51 | class Test { 52 | /** */ 53 | private foo() {} 54 | } 55 | `, 56 | '_$', 57 | 'test.ts' 58 | ).access 59 | ).toBe('private'); 60 | 61 | expect( 62 | evaluate( 63 | ` 64 | class Test { 65 | /** */ 66 | protected foo() {} 67 | } 68 | `, 69 | '_$', 70 | 'test.ts' 71 | ).access 72 | ).toBe('protected'); 73 | 74 | expect( 75 | evaluate( 76 | ` 77 | class Test { 78 | /** */ 79 | public foo() {} 80 | } 81 | `, 82 | '_$', 83 | 'test.ts' 84 | ).access 85 | ).toBe('public'); 86 | 87 | expect( 88 | evaluate( 89 | ` 90 | abstract class Test { 91 | /** */ 92 | public abstract foo(); 93 | } 94 | `, 95 | '_$', 96 | 'test.ts' 97 | ).access 98 | ).toBe('public'); 99 | 100 | expect( 101 | evaluate( 102 | ` 103 | class Test { 104 | /** */ 105 | readonly name: string; 106 | } 107 | `, 108 | '_$', 109 | 'test.ts' 110 | ).readonly 111 | ).toBe(true); 112 | 113 | expect( 114 | evaluate( 115 | ` 116 | interface Test { 117 | /** */ 118 | readonly name: string; 119 | } 120 | `, 121 | '_$', 122 | 'test.ts' 123 | ).readonly 124 | ).toBe(true); 125 | }); 126 | -------------------------------------------------------------------------------- /__tests__/lib/infer/augments.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable no-unused-vars*/ 2 | import inferAugments from '../../../src/infer/augments'; 3 | import parse from '../../../src/parsers/javascript'; 4 | 5 | function toComment(fn, filename) { 6 | return parse( 7 | { 8 | file: filename, 9 | source: fn instanceof Function ? '(' + fn.toString() + ')' : fn 10 | }, 11 | {} 12 | )[0]; 13 | } 14 | 15 | function evaluate(code, filename) { 16 | return inferAugments(toComment(code, filename)); 17 | } 18 | 19 | test('inferAugments', function () { 20 | expect(evaluate('/** */class A extends B {}').augments).toEqual([ 21 | { 22 | name: 'B', 23 | title: 'augments' 24 | } 25 | ]); 26 | 27 | expect(evaluate('/** */interface A extends B, C {}').augments).toEqual([ 28 | { 29 | name: 'B', 30 | title: 'extends' 31 | }, 32 | { 33 | name: 'C', 34 | title: 'extends' 35 | } 36 | ]); 37 | 38 | expect( 39 | evaluate('/** */interface A extends B, C {}', 'test.ts').augments 40 | ).toEqual([ 41 | { 42 | name: 'B', 43 | title: 'extends' 44 | }, 45 | { 46 | name: 'C', 47 | title: 'extends' 48 | } 49 | ]); 50 | }); 51 | -------------------------------------------------------------------------------- /__tests__/lib/infer/finders.js: -------------------------------------------------------------------------------- 1 | import parse from '../../../src/parsers/javascript.js'; 2 | import findTarget from '../../../src/infer/finders'; 3 | 4 | function toComment(fn) { 5 | if (typeof fn == 'function') { 6 | fn = '(' + fn.toString() + ')'; 7 | } 8 | 9 | return parse( 10 | { 11 | source: fn 12 | }, 13 | {} 14 | )[0]; 15 | } 16 | 17 | function evaluate(fn, re) { 18 | return toComment(fn); 19 | } 20 | 21 | test('findTarget', function () { 22 | expect( 23 | findTarget( 24 | toComment(function () { 25 | /** Test */ 26 | const x = 10; 27 | }).context.ast 28 | ).type 29 | ).toBe('VariableDeclarator'); 30 | 31 | expect( 32 | findTarget( 33 | toComment(function () { 34 | const z = {}; 35 | 36 | /** Test */ 37 | z.y = 10; 38 | }).context.ast 39 | ).type 40 | ).toBe('NumericLiteral'); 41 | 42 | expect( 43 | findTarget( 44 | toComment(function () { 45 | const z = { 46 | /** Test */ 47 | y: 10 48 | }; 49 | }).context.ast 50 | ).type 51 | ).toBe('NumericLiteral'); 52 | 53 | expect( 54 | findTarget( 55 | toComment( 56 | ` 57 | /** Test */ 58 | export var z = 10; 59 | ` 60 | ).context.ast 61 | ).type 62 | ).toBe('VariableDeclarator'); 63 | 64 | expect( 65 | findTarget( 66 | toComment( 67 | ` 68 | /** Test */ 69 | export default 10; 70 | ` 71 | ).context.ast 72 | ).type 73 | ).toBe('NumericLiteral'); 74 | }); 75 | -------------------------------------------------------------------------------- /__tests__/lib/infer/implements.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable no-unused-vars*/ 2 | import inferImplements from '../../../src/infer/implements'; 3 | import parse from '../../../src/parsers/javascript'; 4 | 5 | function toComment(fn, filename) { 6 | return parse( 7 | { 8 | file: filename, 9 | source: fn instanceof Function ? '(' + fn.toString() + ')' : fn 10 | }, 11 | {} 12 | )[0]; 13 | } 14 | 15 | function evaluate(code, filename) { 16 | return inferImplements(toComment(code, filename)); 17 | } 18 | 19 | test('inferImplements (flow)', function () { 20 | expect(evaluate('/** */class A implements B {}').implements).toEqual([ 21 | { 22 | name: 'B', 23 | title: 'implements' 24 | } 25 | ]); 26 | 27 | expect(evaluate('/** */class A implements B, C {}').implements).toEqual([ 28 | { 29 | name: 'B', 30 | title: 'implements' 31 | }, 32 | { 33 | name: 'C', 34 | title: 'implements' 35 | } 36 | ]); 37 | }); 38 | 39 | test('inferImplements (typescript)', function () { 40 | expect( 41 | evaluate('/** */class A implements B {}', 'test.ts').implements 42 | ).toEqual([ 43 | { 44 | name: 'B', 45 | title: 'implements' 46 | } 47 | ]); 48 | 49 | expect( 50 | evaluate('/** */class A implements B, C {}', 'test.ts').implements 51 | ).toEqual([ 52 | { 53 | name: 'B', 54 | title: 'implements' 55 | }, 56 | { 57 | name: 'C', 58 | title: 'implements' 59 | } 60 | ]); 61 | }); 62 | -------------------------------------------------------------------------------- /__tests__/lib/infer/properties.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable no-unused-vars*/ 2 | import inferProperties from '../../../src/infer/properties'; 3 | import parse from '../../../src/parsers/javascript'; 4 | 5 | function toComment(fn, filename) { 6 | return parse( 7 | { 8 | file: filename, 9 | source: fn instanceof Function ? '(' + fn.toString() + ')' : fn 10 | }, 11 | {} 12 | )[0]; 13 | } 14 | 15 | function evaluate(code, filename) { 16 | return inferProperties(toComment(code, filename)); 17 | } 18 | 19 | test('inferProperties (flow)', function () { 20 | expect(evaluate('/** */type a = { b: 1 };').properties).toEqual([ 21 | { 22 | lineNumber: 1, 23 | name: 'b', 24 | title: 'property', 25 | type: { 26 | type: 'NumericLiteralType', 27 | value: 1 28 | } 29 | } 30 | ]); 31 | 32 | expect( 33 | evaluate('/** @property {number} b */ type a = { b: 1 };').properties 34 | ).toEqual([ 35 | { 36 | lineNumber: 0, 37 | name: 'b', 38 | title: 'property', 39 | type: { 40 | name: 'number', 41 | type: 'NameExpression' 42 | } 43 | } 44 | ]); 45 | 46 | expect(evaluate('/** */type a = { b: { c: 2 } };').properties).toEqual([ 47 | { 48 | lineNumber: 1, 49 | name: 'b', 50 | title: 'property', 51 | type: { 52 | type: 'RecordType', 53 | fields: [ 54 | { 55 | key: 'c', 56 | type: 'FieldType', 57 | value: { 58 | type: 'NumericLiteralType', 59 | value: 2 60 | } 61 | } 62 | ] 63 | } 64 | } 65 | ]); 66 | }); 67 | 68 | test('inferProperties (typescript)', function () { 69 | expect(evaluate('/** */type a = { b: 1 };', 'test.ts').properties).toEqual([ 70 | { 71 | lineNumber: 1, 72 | name: 'b', 73 | title: 'property', 74 | type: { 75 | type: 'NumericLiteralType', 76 | value: 1 77 | } 78 | } 79 | ]); 80 | 81 | expect( 82 | evaluate('/** @property {number} b */ type a = { b: 1 };', 'test.ts') 83 | .properties 84 | ).toEqual([ 85 | { 86 | lineNumber: 0, 87 | name: 'b', 88 | title: 'property', 89 | type: { 90 | name: 'number', 91 | type: 'NameExpression' 92 | } 93 | } 94 | ]); 95 | 96 | expect( 97 | evaluate('/** */type a = { b: { c: 2 } };', 'test.ts').properties 98 | ).toEqual([ 99 | { 100 | lineNumber: 1, 101 | name: 'b', 102 | title: 'property', 103 | type: { 104 | type: 'RecordType', 105 | fields: [ 106 | { 107 | key: 'c', 108 | type: 'FieldType', 109 | value: { 110 | type: 'NumericLiteralType', 111 | value: 2 112 | } 113 | } 114 | ] 115 | } 116 | } 117 | ]); 118 | }); 119 | -------------------------------------------------------------------------------- /__tests__/lib/input/dependency.js: -------------------------------------------------------------------------------- 1 | import os from 'os'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import dependency from '../../../src/input/dependency.js'; 5 | 6 | function inputs(contents) { 7 | const dirEntry = os.tmpdir(); 8 | const paths = {}; 9 | for (const filename in contents) { 10 | paths[filename] = path.join(dirEntry, '/', filename); 11 | fs.writeFileSync(paths[filename], contents[filename]); 12 | } 13 | return { 14 | paths 15 | }; 16 | } 17 | 18 | test('dependency', async function () { 19 | const { paths, cleanup } = inputs({ 20 | 'index.js': 'module.exports = 1;', 21 | 'requires.js': "module.exports = require('./foo');", 22 | 'foo.js': 'module.exports = 2;' 23 | }); 24 | { 25 | const dependencies = await dependency([paths['index.js']], { 26 | parseExtension: ['.js'] 27 | }); 28 | expect(dependencies.length).toEqual(1); 29 | } 30 | { 31 | const dependencies = await dependency([paths['requires.js']], { 32 | parseExtension: ['.js'] 33 | }); 34 | expect(dependencies.length).toEqual(2); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /__tests__/lib/input/shallow.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import shallow from '../../../src/input/shallow.js'; 3 | import { fileURLToPath } from 'url'; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = path.dirname(__filename); 7 | 8 | test('shallow deps', async function () { 9 | const deps = await shallow( 10 | [path.resolve(path.join(__dirname, '../../fixture/es6.input.js'))], 11 | {} 12 | ); 13 | expect(deps.length).toBe(1); 14 | expect(deps[0]).toBeTruthy(); 15 | }); 16 | 17 | test('shallow deps multi', async function () { 18 | const deps = await shallow( 19 | [ 20 | path.resolve(path.join(__dirname, '../../fixture/es6.input.js')), 21 | path.resolve(path.join(__dirname, '../../fixture/simple.input.js')) 22 | ], 23 | {} 24 | ); 25 | expect(deps.length).toBe(2); 26 | expect(deps[0]).toBeTruthy(); 27 | }); 28 | 29 | test('shallow deps directory', async function () { 30 | const deps = await shallow( 31 | [path.resolve(path.join(__dirname, '../../fixture/html'))], 32 | {} 33 | ); 34 | expect(deps.length).toBe(1); 35 | expect(deps[0].file.match(/input.js/)).toBeTruthy(); 36 | }); 37 | 38 | test('shallow deps literal', async function () { 39 | const obj = { 40 | file: 'foo.js', 41 | source: '//bar' 42 | }; 43 | const deps = await shallow([obj], {}); 44 | expect(deps[0]).toBe(obj); 45 | }); 46 | -------------------------------------------------------------------------------- /__tests__/lib/lint.js: -------------------------------------------------------------------------------- 1 | import parse from '../../src/parsers/javascript.js'; 2 | import { lintComments, formatLint } from '../../src/lint.js'; 3 | 4 | function toComment(fn, filename) { 5 | return parse( 6 | { 7 | file: filename, 8 | source: fn instanceof Function ? '(' + fn.toString() + ')' : fn 9 | }, 10 | {} 11 | )[0]; 12 | } 13 | 14 | function evaluate(fn) { 15 | return lintComments(toComment(fn, 'input.js')); 16 | } 17 | 18 | test('lintComments', function () { 19 | expect( 20 | evaluate(function () { 21 | /** 22 | * @param {foo 23 | */ 24 | }).errors 25 | ).toEqual([ 26 | { message: 'Braces are not balanced' }, 27 | { message: 'Missing or invalid tag name' } 28 | ]); 29 | 30 | expect( 31 | evaluate(function () { 32 | /** 33 | * @param {Object} foo.bar 34 | */ 35 | }).errors 36 | ).toEqual([{ commentLineNumber: 1, message: 'Parent of foo.bar not found' }]); 37 | 38 | expect( 39 | evaluate(function () { 40 | /** 41 | * @param {String} foo 42 | * @param {array} bar 43 | */ 44 | }).errors 45 | ).toEqual([ 46 | { 47 | commentLineNumber: 1, 48 | message: 'type String found, string is standard' 49 | }, 50 | { commentLineNumber: 2, message: 'type array found, Array is standard' } 51 | ]); 52 | 53 | expect( 54 | evaluate(function () { 55 | /** 56 | * @param {string} foo 57 | */ 58 | }).errors 59 | ).toEqual([]); 60 | }); 61 | 62 | test('formatLint', function () { 63 | const comment = evaluate(function () { 64 | // 2 65 | // 3 66 | /** 4 67 | * @param {String} foo 68 | * @param {array} bar 69 | * @param {foo 70 | */ 71 | }); 72 | 73 | const formatted = formatLint([comment]); 74 | 75 | expect(formatted.match(/input.js/g)); 76 | expect(formatted.match(/4:1[^\n]+Braces are not balanced/g)); 77 | expect(formatted.match(/4:1[^\n]+Missing or invalid tag name/g)); 78 | expect(formatted.match(/5:1[^\n]+type String found, string is standard/g)); 79 | expect(formatted.match(/6:1[^\n]+type array found, Array is standard/g)); 80 | expect(formatted.match(/4 warnings/g)); 81 | }); 82 | -------------------------------------------------------------------------------- /__tests__/lib/merge_config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import config from '../../src/config.js'; 3 | import mergeConfig from '../../src/merge_config.js'; 4 | import { fileURLToPath } from 'url'; 5 | 6 | const __filename = fileURLToPath(import.meta.url); 7 | const __dirname = path.dirname(__filename); 8 | 9 | describe('single config tests', function () { 10 | beforeEach(function () { 11 | config.reset(); 12 | }); 13 | 14 | test('Should be failed on bad config', async function () { 15 | try { 16 | await mergeConfig({ config: 'DOES-NOT-EXIST' }); 17 | } catch (err) { 18 | expect(err).toBeTruthy(); 19 | return; 20 | } 21 | return Promise.reject(new Error('should be failed on bad config')); 22 | }); 23 | 24 | test('right merging package configuration', async function () { 25 | const list = [ 26 | 'config', 27 | 'no-package', 28 | 'parseExtension', 29 | 'project-homepage', 30 | 'project-version', 31 | 'project-description' 32 | ]; 33 | await mergeConfig({ 34 | config: path.join(__dirname, '../config_fixture/config.json'), 35 | 'no-package': true, 36 | 'project-name': 'cool Documentation' 37 | }); 38 | 39 | const res = config.globalConfig; 40 | list.forEach(key => delete res[key]); 41 | expect(res).toEqual({ 42 | 'project-name': 'cool Documentation', 43 | foo: 'bar' 44 | }); 45 | }); 46 | 47 | const list = [ 48 | 'config', 49 | 'no-package', 50 | 'parseExtension', 51 | 'project-homepage', 52 | 'project-name', 53 | 'project-version', 54 | 'project-description' 55 | ]; 56 | 57 | [ 58 | [ 59 | { config: path.join(__dirname, '../config_fixture/config.json') }, 60 | { foo: 'bar' } 61 | ], 62 | [ 63 | { 64 | passThrough: true, 65 | config: path.join(__dirname, '../config_fixture/config.json') 66 | }, 67 | { foo: 'bar', passThrough: true } 68 | ], 69 | [ 70 | { 71 | config: path.join(__dirname, '../config_fixture/config_comments.json') 72 | }, 73 | { foo: 'bar' } 74 | ], 75 | [ 76 | { config: path.join(__dirname, '../config_fixture/config.yaml') }, 77 | { foo: 'bar' } 78 | ], 79 | [ 80 | { config: path.join(__dirname, '../config_fixture/config.yml') }, 81 | { foo: 'bar' } 82 | ], 83 | [ 84 | { config: path.join(__dirname, '../config_fixture/config') }, 85 | { foo: 'bar' } 86 | ], 87 | [ 88 | { 89 | config: path.join(__dirname, '../config_fixture/config_links.yml') 90 | }, 91 | { foo: 'hello [link](https://github.com/my/link) world' } 92 | ], 93 | [ 94 | { config: path.join(__dirname, '../config_fixture/config_file.yml') }, 95 | { 96 | toc: [ 97 | { 98 | name: 'snowflake', 99 | file: path.join(__dirname, '../fixture/snowflake.md') 100 | } 101 | ] 102 | } 103 | ] 104 | ].forEach((pair, index) => { 105 | test(`nc(mergeConfig) ${index}`, async function () { 106 | await mergeConfig(Object.assign(pair[0], { 'no-package': true })); 107 | const res = config.globalConfig; 108 | list.forEach(key => delete res[key]); 109 | expect(res).toEqual(pair[1]); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /__tests__/lib/module_filters.js: -------------------------------------------------------------------------------- 1 | import internalOnly from '../../src/module_filters.js'; 2 | 3 | test('moduleFilters.internalOnly', function () { 4 | expect(internalOnly('./foo')).toEqual(true); 5 | expect(internalOnly('foo')).toEqual(false); 6 | }); 7 | -------------------------------------------------------------------------------- /__tests__/lib/nest.js: -------------------------------------------------------------------------------- 1 | import { nestTag } from '../../src/nest.js'; 2 | 3 | // Print a tree of tags in a way that's easy to test. 4 | const printTree = indent => node => 5 | `${new Array(indent + 1).join(' ')}- ${node.name}${ 6 | node.properties ? '\n' : '' 7 | }${(node.properties || []).map(printTree(indent + 1)).join('\n')}`; 8 | 9 | const printNesting = params => 10 | printTree(0)({ properties: nestTag(params), name: 'root' }); 11 | 12 | test('nest params - basic', function () { 13 | const params = [ 14 | 'foo', 15 | 'foo.bar', 16 | 'foo.bar.third', 17 | 'foo.third', 18 | 'foo.third[].baz' 19 | ].map(name => ({ name })); 20 | expect(printNesting(params)).toBe( 21 | `- root 22 | - foo 23 | - foo.bar 24 | - foo.bar.third 25 | - foo.third 26 | - foo.third[].baz` 27 | ); 28 | }); 29 | 30 | test('nest params - multiple roots', function () { 31 | const params = ['a', 'b', 'c'].map(name => ({ name })); 32 | expect(printNesting(params)).toBe( 33 | `- root 34 | - a 35 | - b 36 | - c` 37 | ); 38 | }); 39 | 40 | test('nest params - missing parent', function () { 41 | const params = ['foo', 'foo.bar.third'].map(name => ({ name })); 42 | expect(() => { 43 | nestTag(params); 44 | }).toThrow(); 45 | }); 46 | 47 | test('nest params - #658', function () { 48 | const params = [ 49 | 'state', 50 | 'payload', 51 | 'payload.input_meter_levels', 52 | 'payload.input_meter_levels[].peak', 53 | 'payload.input_meter_levels[].rms', 54 | 'payload.output_meter_levels', 55 | 'payload.output_meter_levels[].peak', 56 | 'payload.output_meter_levels[].rms' 57 | ].map(name => ({ name })); 58 | expect(printNesting(params)).toBe( 59 | `- root 60 | - state 61 | - payload 62 | - payload.input_meter_levels 63 | - payload.input_meter_levels[].peak 64 | - payload.input_meter_levels[].rms 65 | - payload.output_meter_levels 66 | - payload.output_meter_levels[].peak 67 | - payload.output_meter_levels[].rms` 68 | ); 69 | }); 70 | 71 | test('nest params - #554', function () { 72 | const params = [ 73 | 'x', 74 | 'yIn', 75 | 'options', 76 | 'options.sgOption', 77 | 'options.minMaxRatio', 78 | 'options.broadRatio', 79 | 'options.noiseLevel', 80 | 'options.maxCriteria', 81 | 'options.smoothY', 82 | 'options.realTopDetection', 83 | 'options.heightFactor', 84 | 'options.boundaries', 85 | 'options.derivativeThreshold' 86 | ].map(name => ({ name })); 87 | expect(printNesting(params)).toBe( 88 | `- root 89 | - x 90 | - yIn 91 | - options 92 | - options.sgOption 93 | - options.minMaxRatio 94 | - options.broadRatio 95 | - options.noiseLevel 96 | - options.maxCriteria 97 | - options.smoothY 98 | - options.realTopDetection 99 | - options.heightFactor 100 | - options.boundaries 101 | - options.derivativeThreshold` 102 | ); 103 | }); 104 | -------------------------------------------------------------------------------- /__tests__/lib/output/util/formatters.js: -------------------------------------------------------------------------------- 1 | import fromat from '../../../../src/output/util/formatters.js'; 2 | const formatters = fromat(getHref); 3 | 4 | test('formatters.parameters -- long form', function () { 5 | expect(formatters.parameters({})).toEqual('()'); 6 | expect(formatters.parameters({ params: [] })).toEqual('()'); 7 | expect(formatters.parameters({ params: [{ name: 'foo' }] })).toEqual( 8 | '(foo: any)' 9 | ); 10 | expect( 11 | formatters.parameters({ 12 | params: [{ name: 'foo', type: { type: 'OptionalType' } }] 13 | }) 14 | ).toEqual('(foo: any?)'); 15 | }); 16 | 17 | test('formatters.parameters -- short form', function () { 18 | expect(formatters.parameters({}, true)).toEqual('()'); 19 | expect(formatters.parameters({ params: [] }, true)).toEqual('()'); 20 | expect(formatters.parameters({ params: [{ name: 'foo' }] }, true)).toEqual( 21 | '(foo)' 22 | ); 23 | expect( 24 | formatters.parameters( 25 | { 26 | params: [{ name: 'foo', type: { type: 'OptionalType' } }] 27 | }, 28 | true 29 | ) 30 | ).toEqual('(foo?)'); 31 | expect( 32 | formatters.parameters( 33 | { 34 | params: [ 35 | { 36 | title: 'param', 37 | description: 'param', 38 | type: { 39 | type: 'OptionalType', 40 | expression: { 41 | type: 'NameExpression', 42 | name: 'number' 43 | } 44 | }, 45 | name: 'bar', 46 | default: '1' 47 | } 48 | ] 49 | }, 50 | true 51 | ) 52 | ).toEqual('(bar = 1)'); 53 | }); 54 | 55 | function getHref(x) { 56 | return x; 57 | } 58 | -------------------------------------------------------------------------------- /__tests__/lib/parsers/javascript.js: -------------------------------------------------------------------------------- 1 | import removePosition from '../../../src/remark-remove-position.js'; 2 | import { remark } from 'remark'; 3 | const remarkParse = remark().use(removePosition).parse; 4 | import parse from '../../../src/parsers/javascript'; 5 | 6 | function toComments(source, filename, opts) { 7 | source = typeof source === 'string' ? source : '(' + source.toString() + ')'; 8 | return parse( 9 | { 10 | file: filename || 'test.js', 11 | source 12 | }, 13 | opts || {} 14 | ); 15 | } 16 | 17 | test('parse - leading comment', function () { 18 | expect( 19 | toComments(function () { 20 | /** one */ 21 | /** two */ 22 | function two() {} 23 | }).map(function (c) { 24 | return c.description; 25 | }) 26 | ).toEqual([remarkParse('one'), remarkParse('two')]); 27 | }); 28 | 29 | test('parse - trailing comment', function () { 30 | expect( 31 | toComments(function () { 32 | /** one */ 33 | function one() {} 34 | /** two */ 35 | }).map(function (c) { 36 | return c.description; 37 | }) 38 | ).toEqual([remarkParse('one'), remarkParse('two')]); 39 | }); 40 | 41 | test('parse - unknown tag', function () { 42 | expect( 43 | toComments(function () { 44 | /** @unknown */ 45 | })[0].tags[0].title 46 | ).toBe('unknown'); 47 | }); 48 | 49 | test('parse - error', function () { 50 | expect( 51 | toComments(function () { 52 | /** @param {foo */ 53 | })[0].errors 54 | ).toEqual([ 55 | { message: 'Braces are not balanced' }, 56 | { message: 'Missing or invalid tag name' } 57 | ]); 58 | }); 59 | 60 | test('parse - document exported', function () { 61 | expect( 62 | toComments( 63 | ` 64 | export class C {} 65 | ` 66 | ).length 67 | ).toBe(0); 68 | expect( 69 | toComments( 70 | ` 71 | export class C {} 72 | `, 73 | 'test.js', 74 | { documentExported: true } 75 | ).length 76 | ).toBe(1); 77 | expect( 78 | toComments( 79 | ` 80 | export class C { 81 | method() {} 82 | } 83 | `, 84 | 'test.js', 85 | { documentExported: true } 86 | ).length 87 | ).toBe(2); 88 | }); 89 | 90 | test('parse - constructor comments', function () { 91 | expect( 92 | toComments(` 93 | class Test { 94 | /** @hideconstructor */ 95 | constructor() {} 96 | } 97 | `).length 98 | ).toBe(0); 99 | 100 | expect( 101 | toComments(` 102 | /** Test */ 103 | export class Test { 104 | /** @hideconstructor */ 105 | constructor() {} 106 | } 107 | `)[0].constructorComment 108 | ).toBeDefined(); 109 | }); 110 | -------------------------------------------------------------------------------- /__tests__/lib/parsers/parse_to_ast.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { createRequire } from 'module'; 3 | import { commentToFlow, parseToAst } from '../../../src/parsers/parse_to_ast'; 4 | 5 | const require = createRequire(import.meta.url); 6 | 7 | describe('flow comments', () => { 8 | const f = require.resolve('../../fixture/flow/comment-types'); 9 | const src = fs.readFileSync(f, 'utf8'); 10 | 11 | test('preserve line numbers', () => { 12 | const out = commentToFlow(src); 13 | const linesSrc = src.split(/\n/); 14 | const linesOut = out.split(/\n/); 15 | 16 | expect(linesOut).toHaveLength(linesSrc.length); 17 | expect(linesSrc[14]).toEqual(linesOut[14]); 18 | }); 19 | 20 | test('valid js', () => { 21 | expect(() => parseToAst(src, 'test.js')).not.toThrowError(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /__tests__/lib/walk.js: -------------------------------------------------------------------------------- 1 | import walk from '../../src/walk.js'; 2 | 3 | describe('walk', function () { 4 | test('flat comments', function () { 5 | const comments = [{ name: 'Tom' }]; 6 | 7 | function renamer(comment, options) { 8 | if (options) { 9 | comment.name = options.name; 10 | } else { 11 | comment.name = 'Tim'; 12 | } 13 | } 14 | 15 | expect(walk(comments, renamer)).toEqual([{ name: 'Tim' }]); 16 | 17 | expect(walk(comments, renamer, { name: 'John' })).toEqual([ 18 | { name: 'John' } 19 | ]); 20 | }); 21 | 22 | test('nested comments', function () { 23 | const comments = [ 24 | { 25 | name: 'Tom', 26 | members: { 27 | static: [ 28 | { 29 | name: 'Billy' 30 | } 31 | ] 32 | } 33 | } 34 | ]; 35 | 36 | function renamer(comment, options) { 37 | if (options) { 38 | comment.name = options.name; 39 | } else { 40 | comment.name = 'Tim'; 41 | } 42 | } 43 | 44 | expect(walk(comments, renamer)).toEqual([ 45 | { 46 | name: 'Tim', 47 | members: { 48 | static: [ 49 | { 50 | name: 'Tim' 51 | } 52 | ] 53 | } 54 | } 55 | ]); 56 | 57 | expect( 58 | walk(comments, renamer, { 59 | name: 'Bob' 60 | }) 61 | ).toEqual([ 62 | { 63 | name: 'Bob', 64 | members: { 65 | static: [ 66 | { 67 | name: 'Bob' 68 | } 69 | ] 70 | } 71 | } 72 | ]); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /__tests__/linker.js: -------------------------------------------------------------------------------- 1 | import LinkerStack from '../src/output/util/linker_stack.js'; 2 | 3 | test('linkerStack', function () { 4 | const linkerStack = new LinkerStack({}); 5 | 6 | expect(linkerStack.link('string')).toBe( 7 | 'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String' 8 | ); 9 | 10 | expect( 11 | new LinkerStack({ 12 | paths: { 13 | Point: 'http://geojson.org/geojson-spec.html#point' 14 | } 15 | }).link('Point') 16 | ).toBe('http://geojson.org/geojson-spec.html#point'); 17 | 18 | expect( 19 | new LinkerStack({ 20 | paths: { 21 | Image: 'http://custom.com/' 22 | } 23 | }).link('Image') 24 | ).toBe('http://custom.com/'); 25 | 26 | const linker = new LinkerStack({ 27 | paths: { 28 | Image: 'http://custom.com/' 29 | } 30 | }); 31 | 32 | linker.namespaceResolver( 33 | [ 34 | { 35 | namespace: 'Image' 36 | } 37 | ], 38 | function (namespace) { 39 | return '#' + namespace; 40 | } 41 | ); 42 | 43 | expect(linker.link('Image')).toBe('#Image'); 44 | }); 45 | -------------------------------------------------------------------------------- /__tests__/misc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-theme", 3 | "description": "", 4 | "main": "index.js", 5 | "version": "0.0.0" 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/utils.js: -------------------------------------------------------------------------------- 1 | import walk from '../src/walk.js'; 2 | import http from 'http'; 3 | 4 | export function normalize(comments) { 5 | return walk(comments, function (comment) { 6 | const hasGithub = !!comment.context.github; 7 | const path = comment.context.path; 8 | comment.context = { 9 | loc: comment.context.loc 10 | }; 11 | if (hasGithub) { 12 | comment.context.github = { 13 | path: '[github]', 14 | url: '[github]' 15 | }; 16 | } 17 | if (path) { 18 | comment.context.path = path; 19 | } 20 | }); 21 | } 22 | 23 | export const mockRepo = { 24 | master: { 25 | '/my': { 26 | repository: { 27 | path: { 28 | '.git': { 29 | HEAD: 'ref: refs/heads/master', 30 | config: 31 | '[remote "origin"]\n' + 32 | 'url = git@github.com:foo/bar.git\n' + 33 | 'fetch = +refs/heads/*:refs/remotes/origin/*', 34 | refs: { 35 | heads: { 36 | master: 'this_is_the_sha' 37 | } 38 | } 39 | }, 40 | 'index.js': 'module.exports = 42;' 41 | } 42 | } 43 | } 44 | }, 45 | detached: { 46 | '/my': { 47 | repository: { 48 | path: { 49 | '.git': { 50 | HEAD: 'e4cb2ffe677571d0503e659e4e64e01f45639c62', 51 | config: 52 | '[remote "origin"]\n' + 53 | 'url = git@github.com:foo/bar.git\n' + 54 | 'fetch = +refs/heads/*:refs/remotes/origin/*' 55 | }, 56 | 'index.js': 'module.exports = 42;' 57 | } 58 | } 59 | } 60 | }, 61 | submodule: { 62 | '/my': { 63 | repository: { 64 | 'my.submodule': { 65 | '.git': 'gitdir: ../.git/modules/my.submodule', 66 | 'index.js': 'module.exports = 42;' 67 | }, 68 | '.git': { 69 | config: 70 | '[submodule "my.submodule"]\n' + 71 | 'url = https://github.com/foo/bar\n' + 72 | 'active = true', 73 | modules: { 74 | 'my.submodule': { 75 | HEAD: 'ref: refs/heads/master', 76 | refs: { 77 | heads: { 78 | master: 'this_is_the_sha' 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | }, 87 | malformed: { 88 | '/my': { 89 | repository: { 90 | path: { 91 | '.git': {}, 92 | 'index.js': 'module.exports = 42;' 93 | } 94 | } 95 | } 96 | }, 97 | enterprise: { 98 | '/my': { 99 | repository: { 100 | path: { 101 | '.git': { 102 | HEAD: 'ref: refs/heads/master', 103 | config: 104 | '[remote "origin"]\n' + 105 | 'url = git@github.enterprise.com:foo/bar.git\n' + 106 | 'fetch = +refs/heads/*:refs/remotes/origin/*', 107 | refs: { 108 | heads: { 109 | master: 'this_is_the_sha' 110 | } 111 | } 112 | }, 113 | 'index.js': 'module.exports = 42;' 114 | } 115 | } 116 | } 117 | } 118 | }; 119 | -------------------------------------------------------------------------------- /bin/documentation.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* eslint no-console: 0 */ 4 | 5 | import yargs from 'yargs'; 6 | import { hideBin } from 'yargs/helpers'; 7 | import commands from '../src/commands/index.js'; 8 | 9 | yargs(hideBin(process.argv)) 10 | .strict() 11 | .command(commands.build) 12 | .command(commands.lint) 13 | .command(commands.readme) 14 | .example('documentation build foo.js -f md > API.md') 15 | .example('documentation readme index.js -s "API Docs" --github') 16 | .version() 17 | .usage( 18 | `Usage: 19 | 20 | # generate markdown docs for index.js and files it references 21 | $0 build index.js -f md 22 | 23 | # generate html docs for all files in src 24 | $0 build src/** -f html -o docs 25 | 26 | # document index.js, ignoring any files it requires or imports 27 | $0 build index.js -f md --shallow 28 | 29 | # build, serve, and live-update html docs for app.js 30 | $0 serve app.js 31 | 32 | # validate JSDoc syntax in util.js 33 | $0 lint util.js 34 | 35 | # update the API section of README.md with docs from index.js 36 | $0 readme index.js --section=API 37 | 38 | # build docs for all values exported by index.js 39 | $0 build --document-exported index.js 40 | ` 41 | ) 42 | .recommendCommands() 43 | .help().argv; 44 | -------------------------------------------------------------------------------- /declarations/comment.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint no-use-before-define: 0 */ 3 | type DocumentationConfig = { 4 | inferPrivate?: string, 5 | noPackage?: boolean, 6 | toc?: Array, 7 | paths?: { [key: string]: number }, 8 | access?: Array, 9 | defaultGlobals?: boolean, 10 | defaultGlobalsEnvs?: Array, 11 | external?: Array, 12 | theme: string, 13 | requireExtension?: Array, 14 | parseExtension: Array, 15 | noReferenceLinks?: boolean, 16 | markdownToc?: boolean, 17 | markdownTocMaxDepth?: number, 18 | documentExported?: boolean, 19 | resolve?: string, 20 | hljs?: Object 21 | }; 22 | 23 | type CommentError = { 24 | message: string, 25 | commentLineNumber?: number 26 | }; 27 | 28 | type DoctrineType = { 29 | elements?: Array, 30 | expression?: DoctrineType, 31 | applications?: Array, 32 | type: string, 33 | name?: string 34 | }; 35 | 36 | type CommentLoc = { 37 | start: { 38 | line: number 39 | }, 40 | end: { 41 | line: number 42 | } 43 | }; 44 | 45 | type SourceFile = { 46 | source?: string, 47 | file: string 48 | }; 49 | 50 | type CommentContext = { 51 | sortKey: string, 52 | file: string, 53 | ast?: Object, 54 | loc: CommentLoc, 55 | code: string, 56 | github?: CommentContextGitHub 57 | }; 58 | 59 | type CommentContextGitHub = { 60 | path: string, 61 | url: string 62 | }; 63 | 64 | type CommentTag = { 65 | name?: string, 66 | title: string, 67 | description?: Object, 68 | default?: any, 69 | lineNumber?: number, 70 | type?: DoctrineType, 71 | properties?: Array, 72 | readonly?: boolean 73 | }; 74 | 75 | type Comment = { 76 | errors: Array, 77 | tags: Array, 78 | 79 | augments: Array, 80 | examples: Array, 81 | implements: Array, 82 | params: Array, 83 | properties: Array, 84 | returns: Array, 85 | sees: Array, 86 | throws: Array, 87 | todos: Array, 88 | 89 | description?: Remark, 90 | summary?: Remark, 91 | deprecated?: Remark, 92 | classdesc?: Remark, 93 | 94 | members: CommentMembers, 95 | constructorComment?: Comment, 96 | 97 | name?: string, 98 | kind?: Kind, 99 | 100 | memberof?: string, 101 | scope?: Scope, 102 | access?: Access, 103 | readonly?: boolean, 104 | abstract?: boolean, 105 | generator?: boolean, 106 | alias?: string, 107 | 108 | copyright?: string, 109 | author?: string, 110 | license?: string, 111 | version?: string, 112 | since?: string, 113 | lends?: string, 114 | override?: boolean, 115 | hideconstructor?: true, 116 | 117 | type?: DoctrineType, 118 | 119 | context: CommentContext, 120 | loc: CommentLoc, 121 | 122 | path?: Array<{ 123 | name: string, 124 | scope: Scope 125 | }>, 126 | 127 | ignore?: boolean 128 | }; 129 | 130 | type CommentMembers = { 131 | static: Array, 132 | instance: Array, 133 | events: Array, 134 | global: Array, 135 | inner: Array 136 | }; 137 | 138 | type CommentExample = { 139 | caption?: string, 140 | description?: Object 141 | }; 142 | 143 | type Remark = { 144 | type: string, 145 | children: Array 146 | }; 147 | 148 | type Access = 'private' | 'public' | 'protected'; 149 | type Scope = 'instance' | 'static' | 'inner' | 'global'; 150 | type Kind = 151 | | 'class' 152 | | 'constant' 153 | | 'event' 154 | | 'external' 155 | | 'file' 156 | | 'function' 157 | | 'member' 158 | | 'mixin' 159 | | 'module' 160 | | 'namespace' 161 | | 'typedef' 162 | | 'interface'; 163 | -------------------------------------------------------------------------------- /docs/CONFIG.md: -------------------------------------------------------------------------------- 1 | # Configuring documentation.js 2 | 3 | Configuration is a completely optional step for generating documentation 4 | with documentation.js. The tool is designed to accept any JSDoc-annotated 5 | source code and automatically generate output. 6 | 7 | Configuration - a `documentation.yml` file - will accomplish two goals: 8 | 9 | * Organization: you can put top level documentation in order of importance 10 | * Narration: you can add narrative sections - plain English writing - in 11 | between autogenerated API documentation. 12 | 13 | Specify the configuration file with the `--config` command-line option. 14 | ```sh 15 | $ documentation build --config documentation.yml ... 16 | ``` 17 | 18 | Here's how `documentation.yml` works: 19 | 20 | ```yml 21 | toc: 22 | - Map 23 | - name: Geography 24 | description: | 25 | These are Mapbox GL JS's ways of representing locations 26 | and areas on the sphere. 27 | - LngLat 28 | - LngLatBounds 29 | ``` 30 | 31 | This puts the top level API documentation for the `Map`, `LngLat`, and `LngLatBounds` 32 | items in the given order, and inserts a narrative item titled `Geography` 33 | after the section on maps. The `description` property of that narrative item 34 | is interpreted as Markdown. 35 | If you would like reuse your existing markdown files or just keep the content separate from the configuration you can use the `file` property. It is a filename it will be resolved against the directory that the `documentation.yml` file resides in. 36 | 37 | So with a `documentation.yml` file like this 38 | 39 | ```yml 40 | toc: 41 | - Map 42 | - name: Geography 43 | file: geo.md 44 | - LngLat 45 | - LngLatBounds 46 | ``` 47 | 48 | and a file `geo.md` 49 | 50 | ```markdown 51 | These are Mapbox GL JS's ways of representing locations 52 | and areas on the sphere. 53 | ``` 54 | 55 | it would produce the same output as the previous example. 56 | 57 | ## Groups 58 | 59 | The `children` property can be used to group content under headings instead of just arranging them in order. Example: 60 | 61 | ```yml 62 | toc: 63 | - name: Geography 64 | children: 65 | - Map 66 | - LngLat 67 | - LngLatBounds 68 | - name: Navigation 69 | description: | 70 | Here are some helper functions for navigation. 71 | children: 72 | - shortestPath 73 | - salesman 74 | ``` 75 | -------------------------------------------------------------------------------- /docs/POLYGLOT.md: -------------------------------------------------------------------------------- 1 | 🚨 Polyglot mode is now deprecated. It will be replaced by a pluggable 2 | input system in future versions. 🚨 3 | -------------------------------------------------------------------------------- /docs/THEMING.md: -------------------------------------------------------------------------------- 1 | Documentation.js supports customizable themes for HTML output. A theme is a Node.js 2 | module that exports a single function with the following signature: 3 | 4 | ``` 5 | /** 6 | * @function 7 | * @param {Array} comments - an array of comments to be output 8 | * @param {Object} options - theme options 9 | * @param {ThemeCallback} callback - see below 10 | */ 11 | 12 | /** 13 | * @callback ThemeCallback 14 | * @param {?Error} error 15 | * @param {?Array} output 16 | */ 17 | ``` 18 | 19 | The theme function should call the callback with either an error, if one occurs, 20 | or an array of [vinyl](https://github.com/gulpjs/vinyl) `File` objects. 21 | 22 | The theme is free to implement HTML generation however it chooses. See 23 | [the default theme](https://github.com/documentationjs/documentation/tree/master/src/default_theme) 24 | for some ideas. 25 | 26 | ### Customizing the Default Theme 27 | 28 | **Instructions** 29 | 30 | - Copy contents of `default_theme` folder (noted above) into a new folder in your project. One way to do it is to create a new git repository with the folder contents and add this line to your `package.json` `devDependencies` section: `"docjs-theme": "my-gh-username/reponame"`. That way when you install dependencies, your new theme will be in the projects `node_modules` folder. 31 | 32 | - In the folder you created, replace `require('../')` on lines 8 and 9 of `index.js` with `require('documentation')` and save. 33 | 34 | - You can now make changes that will show up when you generate your docs using your theme. Example `package.json` `scripts` entry: `"documentation build index.js -f html -o docs --theme node_modules/docjs-theme"` 35 | 36 | #### Changes to Default Theme Via documentation.yml 37 | 38 | If a documentation.yml file is used to establish a table of contents for your documentation, small changes to the default style can be made via a 59 | 60 | 61 | ### Sub Section header 62 | Text that describes the section and sub-section here. 63 | ``` 64 | Any changes to elements and classes that also exist in the standard theme will be overwritten by what is in the documentation.yml. This opens up the possibility of the same CSS being defined twice, which can be confusing and is not best practice. However, it is easy to change HTML style this way. Recommend only using classes defined this way that do not exist in the standard documentation.js theme. 65 | 66 | ### Theming Markdown 67 | 68 | The default Markdown generator for documentation.js isn't customizable - instead 69 | of a plain-text theme, it's generated by creating an AST and then rendering 70 | it with [remark](https://remark.js.org/). If you need something extra in Markdown, 71 | you can either rally for that thing to be included in the default theme, 72 | or you can hack around it by using an HTML theme that outputs Markdown. 73 | -------------------------------------------------------------------------------- /docs/TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | ## Installing documentation with npm v1 or v2 yields a gigantic `node_modules` 2 | 3 | Unfortunately, we need to recommend: use **yarn or npm v3** to install documentation. 4 | npm v1 and v2 are unable to properly de-duplicate dependencies, so they will 5 | create wastefully large `node_modules` directories under documentation. 6 | 7 | ## documentation is inferring _too much_ about my code 8 | 9 | If you've written very dynamic JavaScript, all of documentation's intelligence 10 | might not understand it. If: 11 | 12 | * documentation is **wrong** in a clear way, please [open a descriptive issue](https://github.com/documentationjs/documentation/issues) with a code snippet and we can help! 13 | * if it's **right** technically but you want to control your docs entirely, 14 | use the `@name` tag to declare the name of the thing (function, variable, etc) 15 | you're referring to, and the tool will not try to infer anything at all. 16 | 17 | ## Error: watch ENOSPC 18 | 19 | Linux systems can have a low limit of 'watchable files'. To increase this 20 | limit, follow [the advice in this StackOverflow answer](https://stackoverflow.com/questions/16748737/grunt-watch-error-waiting-fatal-error-watch-enospc/17437601#17437601). 21 | 22 | ## Error: EMFILE: too many open files 23 | 24 | You'll need to increase the `ulimit` maximum for your system: [here's a StackOverflow issue explaining how](https://unix.stackexchange.com/questions/108174/how-to-persistently-control-maximum-system-resource-consumption-on-mac). 25 | -------------------------------------------------------------------------------- /docs/USAGE_NODE.md: -------------------------------------------------------------------------------- 1 | # Using documentation.js as a node library 2 | 3 | You might want to do this if you're 4 | 5 | * building an integration, like our gulp or grunt integrations 6 | * using documentation.js's AST parsing or some other component 7 | * mad science 8 | 9 | Basic concepts: 10 | 11 | * documentation.js takes an array of entry points, which can be filenames 12 | or objects with `source` and `file` members 13 | * generating documentation is a two-step process: parsing, in the 14 | documentation.build and documentation.buildSync methods, and generating 15 | output in documentation.formats.md, json, or html. 16 | 17 | ### Example 18 | 19 | ```js 20 | var documentation = require('./'); 21 | 22 | var docs = documentation.buildSync([{ 23 | source: '/** hi this is a doc\n@name myDoc */', 24 | file: 'direct.js' 25 | }]); 26 | 27 | documentation.formats.md(docs, {}, function(err, res) { 28 | console.log(res); 29 | }); 30 | ``` 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "documentation", 3 | "description": "a documentation generator", 4 | "version": "14.0.3", 5 | "author": "Tom MacWright", 6 | "homepage": "https://documentation.js.org", 7 | "type": "module", 8 | "bin": { 9 | "documentation": "./bin/documentation.js" 10 | }, 11 | "dependencies": { 12 | "@babel/core": "^7.18.10", 13 | "@babel/generator": "^7.18.10", 14 | "@babel/parser": "^7.18.11", 15 | "@babel/traverse": "^7.18.11", 16 | "@babel/types": "^7.18.10", 17 | "chalk": "^5.0.1", 18 | "chokidar": "^3.5.3", 19 | "diff": "^5.1.0", 20 | "doctrine-temporary-fork": "2.1.0", 21 | "git-url-parse": "^13.1.0", 22 | "github-slugger": "1.4.0", 23 | "glob": "^8.0.3", 24 | "globals-docs": "^2.4.1", 25 | "highlight.js": "^11.6.0", 26 | "ini": "^3.0.0", 27 | "js-yaml": "^4.1.0", 28 | "konan": "^2.1.1", 29 | "lodash": "^4.17.21", 30 | "mdast-util-find-and-replace": "^2.2.1", 31 | "mdast-util-inject": "^1.1.0", 32 | "micromark-util-character": "^1.1.0", 33 | "parse-filepath": "^1.0.2", 34 | "pify": "^6.0.0", 35 | "read-pkg-up": "^9.1.0", 36 | "remark": "^14.0.2", 37 | "remark-gfm": "^3.0.1", 38 | "remark-html": "^15.0.1", 39 | "remark-reference-links": "^6.0.1", 40 | "remark-toc": "^8.0.1", 41 | "resolve": "^1.22.1", 42 | "strip-json-comments": "^5.0.0", 43 | "unist-builder": "^3.0.0", 44 | "unist-util-visit": "^4.1.0", 45 | "vfile": "^5.3.4", 46 | "vfile-reporter": "^7.0.4", 47 | "vfile-sort": "^3.0.0", 48 | "yargs": "^17.5.1" 49 | }, 50 | "optionalDependencies": { 51 | "@vue/compiler-sfc": "^3.2.37", 52 | "vue-template-compiler": "^2.7.8" 53 | }, 54 | "devDependencies": { 55 | "chdir": "0.0.0", 56 | "cz-conventional-changelog": "3.3.0", 57 | "documentation-schema": "0.0.1", 58 | "eslint": "^8.21.0", 59 | "eslint-config-prettier": "^8.5.0", 60 | "fs-extra": "^10.1.0", 61 | "husky": "^8.0.1", 62 | "jest": "^28.1.3", 63 | "json-schema": "^0.4.0", 64 | "lint-staged": "^13.0.3", 65 | "mock-fs": "^5.1.4", 66 | "prettier": "^2.7.1", 67 | "standard-version": "^9.5.0", 68 | "tmp": "^0.2.1" 69 | }, 70 | "keywords": [ 71 | "documentation", 72 | "formatter", 73 | "jsdoc", 74 | "jsdoc3", 75 | "parser", 76 | "website" 77 | ], 78 | "license": "ISC", 79 | "main": "src/index.js", 80 | "repository": { 81 | "type": "git", 82 | "url": "git@github.com:documentationjs/documentation.git" 83 | }, 84 | "scripts": { 85 | "build": "npm run doc", 86 | "release": "standard-version", 87 | "format": "prettier --write '{src,__tests__,declarations,bin,default_theme}/**/*.js'", 88 | "doc": "node ./bin/documentation.js build src/index.js -f md --access=public > docs/NODE_API.md", 89 | "self-lint": "node ./bin/documentation.js lint src", 90 | "test": "eslint . && node --experimental-vm-modules node_modules/jest/bin/jest.js", 91 | "test-ci": "eslint . && node --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand", 92 | "prepare": "husky install", 93 | "pre-commit": "lint-staged" 94 | }, 95 | "jest": { 96 | "testPathIgnorePatterns": [ 97 | "/node_modules/", 98 | "utils.js", 99 | "fixture" 100 | ], 101 | "transform": {}, 102 | "collectCoverage": true, 103 | "testEnvironment": "jest-environment-node", 104 | "moduleNameMapper": { 105 | "#(.*)": "/node_modules/$1" 106 | } 107 | }, 108 | "config": { 109 | "commitizen": { 110 | "path": "./node_modules/cz-conventional-changelog" 111 | } 112 | }, 113 | "engines": { 114 | "node": ">=18" 115 | }, 116 | "lint-staged": { 117 | "*.js": [ 118 | "prettier --write" 119 | ] 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /screenshorts/show options.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/screenshorts/show options.jpg -------------------------------------------------------------------------------- /src/commands/build.js: -------------------------------------------------------------------------------- 1 | import { sharedOutputOptions, sharedInputOptions } from './shared_options.js'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import chokidar from 'chokidar'; 5 | import * as documentation from '../index.js'; 6 | import _ from 'lodash'; 7 | 8 | const command = 'build [input..]'; 9 | const describe = 'build documentation'; 10 | 11 | /** 12 | * Add yargs parsing for the build command 13 | * @param {Object} yargs module instance 14 | * @returns {Object} yargs with options 15 | * @private 16 | */ 17 | const builder = Object.assign({}, sharedOutputOptions, sharedInputOptions, { 18 | output: { 19 | describe: 20 | 'output location. omit for stdout, otherwise is a filename ' + 21 | 'for single-file outputs and a directory name for multi-file outputs like html', 22 | default: 'stdout', 23 | alias: 'o' 24 | } 25 | }); 26 | 27 | /* 28 | * The `build` command. Requires either `--output` or the `callback` argument. 29 | * If the callback is provided, it is called with (error, formattedResult); 30 | * otherwise, formatted results are outputted based on the value of `--output`. 31 | * 32 | * The former case, with the callback, is used by the `serve` command, which is 33 | * just a thin wrapper around this one. 34 | */ 35 | const handler = function build(argv) { 36 | let watcher; 37 | argv._handled = true; 38 | 39 | if (!(argv.input && argv.input.length)) { 40 | try { 41 | argv.input = [ 42 | JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf8')) 43 | .main || 'index.js' 44 | ]; 45 | } catch (e) { 46 | throw new Error( 47 | 'documentation was given no files and was not run in a module directory' 48 | ); 49 | } 50 | } 51 | 52 | if (argv.f === 'html' && argv.o === 'stdout') { 53 | throw new Error( 54 | 'The HTML output mode requires a destination directory set with -o' 55 | ); 56 | } 57 | 58 | function generator() { 59 | return documentation 60 | .build(argv.input, argv) 61 | .then(comments => 62 | documentation.formats[argv.format](comments, argv).then(onFormatted) 63 | ) 64 | .catch(err => { 65 | /* eslint no-console: 0 */ 66 | if (err instanceof Error) { 67 | console.error(err.stack); 68 | } else { 69 | console.error(err); 70 | } 71 | process.exit(1); 72 | }); 73 | } 74 | 75 | function onFormatted(output) { 76 | if (argv.watch) { 77 | updateWatcher(); 78 | } 79 | 80 | if (!output) { 81 | return; 82 | } 83 | 84 | if (argv.output === 'stdout') { 85 | if (argv.watch) { 86 | // In watch mode, clear the screen first to make updated outputs 87 | // obvious. 88 | process.stdout.write('\u001b[2J'); 89 | } 90 | process.stdout.write(output); 91 | } else { 92 | fs.writeFileSync(argv.output, output); 93 | } 94 | } 95 | 96 | function updateWatcher() { 97 | if (!watcher) { 98 | watcher = chokidar.watch(argv.input); 99 | watcher.on('all', _.debounce(generator, 300)); 100 | } 101 | documentation 102 | .expandInputs(argv.input, argv) 103 | .then(files => 104 | watcher.add( 105 | files.map(data => (typeof data === 'string' ? data : data.file)) 106 | ) 107 | ); 108 | } 109 | 110 | return generator(); 111 | }; 112 | 113 | export default { command, describe, builder, handler }; 114 | -------------------------------------------------------------------------------- /src/commands/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Maps command name to a command plugin module. Each command plugin module 3 | * must export a function that takes (documentation, parsedArgs), where 4 | * documentation is just the main module (index.js), and parsedArgs is 5 | * { inputs, options, command, commandOptions } 6 | * 7 | * Command modules should also export a `description`, which will be used in 8 | * the main CLI help, and optionally a `parseArgs(yargs, parentArgv)` function 9 | * to parse additional arguments. 10 | */ 11 | import build from './build.js'; 12 | import lint from './lint.js'; 13 | import readme from './readme.js'; 14 | 15 | export default { build, lint, readme }; 16 | -------------------------------------------------------------------------------- /src/commands/lint.js: -------------------------------------------------------------------------------- 1 | import * as documentation from '../index.js'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import { sharedInputOptions } from './shared_options.js'; 5 | 6 | /* eslint no-console: 0 */ 7 | 8 | const command = 'lint [input..]'; 9 | const description = 'check for common style and uniformity mistakes'; 10 | const builder = { 11 | shallow: sharedInputOptions.shallow 12 | }; 13 | 14 | /** 15 | * Wrap around the documentation.lint method and add the additional 16 | * behavior of printing to stdout and setting an exit status. 17 | * 18 | * @param {Object} argv cli arguments 19 | * @returns {undefined} has side-effects 20 | * @private 21 | */ 22 | const handler = function (argv) { 23 | argv._handled = true; 24 | if (!argv.input.length) { 25 | try { 26 | argv.input = [ 27 | JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf8')) 28 | .main || 'index.js' 29 | ]; 30 | } catch (e) { 31 | throw new Error( 32 | 'documentation was given no files and was not run in a module directory' 33 | ); 34 | } 35 | } 36 | documentation 37 | .lint(argv.input, argv) 38 | .then(lintOutput => { 39 | if (lintOutput) { 40 | console.log(lintOutput); 41 | process.exit(1); 42 | } else { 43 | process.exit(0); 44 | } 45 | }) 46 | .catch(err => { 47 | /* eslint no-console: 0 */ 48 | console.error(err); 49 | process.exit(1); 50 | }); 51 | }; 52 | 53 | export default { command, description, builder, handler }; 54 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const defaultConfig = { 2 | // package.json ignored and don't get project infromation 3 | 'no-package': false, 4 | // Extenstions which by dafault are parse 5 | parseExtension: ['.mjs', '.js', '.jsx', '.es5', '.es6', '.vue', '.ts', '.tsx'] 6 | }; 7 | 8 | function normalaze(config, global) { 9 | if (config.parseExtension) { 10 | config.parseExtension = Array.from( 11 | new Set([...config.parseExtension, ...global.parseExtension]) 12 | ); 13 | } 14 | 15 | return config; 16 | } 17 | 18 | export default { 19 | globalConfig: { 20 | ...defaultConfig 21 | }, 22 | reset() { 23 | this.globalConfig = { ...defaultConfig }; 24 | this.globalConfig.parseExtension = [...defaultConfig.parseExtension]; 25 | }, 26 | add(parameters) { 27 | Object.assign(this.globalConfig, normalaze(parameters, this.globalConfig)); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/default_theme/README.md: -------------------------------------------------------------------------------- 1 | # the default theme 2 | 3 | ![](screenshot.png) 4 | 5 | This is the default theme for [documentationjs](https://github.com/documentationjs): 6 | it consists of underscore templates and a few assets: a [highlight.js](https://highlightjs.org/) 7 | theme and [basscss](https://basscss.com/) as a basic CSS framework. 8 | 9 | This is bundled by default in documentation: it is the default theme. 10 | 11 | The contents are the following: 12 | 13 | * `index._`, the main template that defines the document structure 14 | * `section._`, a partial used to render each chunk of documentation 15 | * `assets/*`, any assets, including CSS & JS 16 | -------------------------------------------------------------------------------- /src/default_theme/assets/bass-addons.css: -------------------------------------------------------------------------------- 1 | .input { 2 | font-family: inherit; 3 | display: block; 4 | width: 100%; 5 | height: 2rem; 6 | padding: .5rem; 7 | margin-bottom: 1rem; 8 | border: 1px solid #ccc; 9 | font-size: .875rem; 10 | border-radius: 3px; 11 | box-sizing: border-box; 12 | } 13 | -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/EOT/SourceCodePro-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/EOT/SourceCodePro-Bold.eot -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/EOT/SourceCodePro-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/EOT/SourceCodePro-Regular.eot -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/OTF/SourceCodePro-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/OTF/SourceCodePro-Bold.otf -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/OTF/SourceCodePro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/OTF/SourceCodePro-Regular.otf -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/TTF/SourceCodePro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/TTF/SourceCodePro-Bold.ttf -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/TTF/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/TTF/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentationjs/documentation/8d029e735d2661c8f5e097c285050c7abe5edf6e/src/default_theme/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 -------------------------------------------------------------------------------- /src/default_theme/assets/fonts/source-code-pro.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'Source Code Pro'; 3 | font-weight: 400; 4 | font-style: normal; 5 | font-stretch: normal; 6 | src: url('EOT/SourceCodePro-Regular.eot') format('embedded-opentype'), 7 | url('WOFF2/TTF/SourceCodePro-Regular.ttf.woff2') format('woff2'), 8 | url('WOFF/OTF/SourceCodePro-Regular.otf.woff') format('woff'), 9 | url('OTF/SourceCodePro-Regular.otf') format('opentype'), 10 | url('TTF/SourceCodePro-Regular.ttf') format('truetype'); 11 | } 12 | 13 | @font-face{ 14 | font-family: 'Source Code Pro'; 15 | font-weight: 700; 16 | font-style: normal; 17 | font-stretch: normal; 18 | src: url('EOT/SourceCodePro-Bold.eot') format('embedded-opentype'), 19 | url('WOFF2/TTF/SourceCodePro-Bold.ttf.woff2') format('woff2'), 20 | url('WOFF/OTF/SourceCodePro-Bold.otf.woff') format('woff'), 21 | url('OTF/SourceCodePro-Bold.otf') format('opentype'), 22 | url('TTF/SourceCodePro-Bold.ttf') format('truetype'); 23 | } 24 | -------------------------------------------------------------------------------- /src/default_theme/assets/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .diff .hljs-header, 18 | .hljs-javadoc { 19 | color: #998; 20 | font-style: italic; 21 | } 22 | 23 | .hljs-keyword, 24 | .css .rule .hljs-keyword, 25 | .hljs-winutils, 26 | .nginx .hljs-title, 27 | .hljs-subst, 28 | .hljs-request, 29 | .hljs-status { 30 | color: #1184CE; 31 | } 32 | 33 | .hljs-number, 34 | .hljs-hexcolor, 35 | .ruby .hljs-constant { 36 | color: #ed225d; 37 | } 38 | 39 | .hljs-string, 40 | .hljs-tag .hljs-value, 41 | .hljs-phpdoc, 42 | .hljs-dartdoc, 43 | .tex .hljs-formula { 44 | color: #ed225d; 45 | } 46 | 47 | .hljs-title, 48 | .hljs-id, 49 | .scss .hljs-preprocessor { 50 | color: #900; 51 | font-weight: bold; 52 | } 53 | 54 | .hljs-list .hljs-keyword, 55 | .hljs-subst { 56 | font-weight: normal; 57 | } 58 | 59 | .hljs-class .hljs-title, 60 | .hljs-type, 61 | .vhdl .hljs-literal, 62 | .tex .hljs-command { 63 | color: #458; 64 | font-weight: bold; 65 | } 66 | 67 | .hljs-tag, 68 | .hljs-tag .hljs-title, 69 | .hljs-rules .hljs-property, 70 | .django .hljs-tag .hljs-keyword { 71 | color: #000080; 72 | font-weight: normal; 73 | } 74 | 75 | .hljs-attribute, 76 | .hljs-variable, 77 | .lisp .hljs-body { 78 | color: #008080; 79 | } 80 | 81 | .hljs-regexp { 82 | color: #009926; 83 | } 84 | 85 | .hljs-symbol, 86 | .ruby .hljs-symbol .hljs-string, 87 | .lisp .hljs-keyword, 88 | .clojure .hljs-keyword, 89 | .scheme .hljs-keyword, 90 | .tex .hljs-special, 91 | .hljs-prompt { 92 | color: #990073; 93 | } 94 | 95 | .hljs-built_in { 96 | color: #0086b3; 97 | } 98 | 99 | .hljs-preprocessor, 100 | .hljs-pragma, 101 | .hljs-pi, 102 | .hljs-doctype, 103 | .hljs-shebang, 104 | .hljs-cdata { 105 | color: #999; 106 | font-weight: bold; 107 | } 108 | 109 | .hljs-deletion { 110 | background: #fdd; 111 | } 112 | 113 | .hljs-addition { 114 | background: #dfd; 115 | } 116 | 117 | .diff .hljs-change { 118 | background: #0086b3; 119 | } 120 | 121 | .hljs-chunk { 122 | color: #aaa; 123 | } 124 | -------------------------------------------------------------------------------- /src/default_theme/assets/split.css: -------------------------------------------------------------------------------- 1 | .gutter { 2 | background-color: #f5f5f5; 3 | background-repeat: no-repeat; 4 | background-position: 50%; 5 | } 6 | 7 | .gutter.gutter-vertical { 8 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII='); 9 | cursor: ns-resize; 10 | } 11 | 12 | .gutter.gutter-horizontal { 13 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); 14 | cursor: ew-resize; 15 | } 16 | -------------------------------------------------------------------------------- /src/default_theme/assets/style.css: -------------------------------------------------------------------------------- 1 | .documentation { 2 | font-family: Helvetica, sans-serif; 3 | color: #666; 4 | line-height: 1.5; 5 | background: #f5f5f5; 6 | } 7 | 8 | .black { 9 | color: #666; 10 | } 11 | 12 | .bg-white { 13 | background-color: #fff; 14 | } 15 | 16 | h4 { 17 | margin: 20px 0 10px 0; 18 | } 19 | 20 | .documentation h3 { 21 | color: #000; 22 | } 23 | 24 | .border-bottom { 25 | border-color: #ddd; 26 | } 27 | 28 | a { 29 | color: #1184ce; 30 | text-decoration: none; 31 | } 32 | 33 | .documentation a[href]:hover { 34 | text-decoration: underline; 35 | } 36 | 37 | a:hover { 38 | cursor: pointer; 39 | } 40 | 41 | .py1-ul li { 42 | padding: 5px 0; 43 | } 44 | 45 | .max-height-100 { 46 | max-height: 100%; 47 | } 48 | 49 | .height-viewport-100 { 50 | height: 100vh; 51 | } 52 | 53 | section:target h3 { 54 | font-weight: 700; 55 | } 56 | 57 | .documentation td, 58 | .documentation th { 59 | padding: 0.25rem 0.25rem; 60 | } 61 | 62 | h1:hover .anchorjs-link, 63 | h2:hover .anchorjs-link, 64 | h3:hover .anchorjs-link, 65 | h4:hover .anchorjs-link { 66 | opacity: 1; 67 | } 68 | 69 | .fix-3 { 70 | width: 25%; 71 | max-width: 244px; 72 | } 73 | 74 | .fix-3 { 75 | width: 25%; 76 | max-width: 244px; 77 | } 78 | 79 | @media (min-width: 52em) { 80 | .fix-margin-3 { 81 | margin-left: 25%; 82 | } 83 | } 84 | 85 | .pre, 86 | pre, 87 | code, 88 | .code { 89 | font-family: Source Code Pro, Menlo, Consolas, Liberation Mono, monospace; 90 | font-size: 14px; 91 | } 92 | 93 | .fill-light { 94 | background: #f9f9f9; 95 | } 96 | 97 | .width2 { 98 | width: 1rem; 99 | } 100 | 101 | .input { 102 | font-family: inherit; 103 | display: block; 104 | width: 100%; 105 | height: 2rem; 106 | padding: 0.5rem; 107 | margin-bottom: 1rem; 108 | border: 1px solid #ccc; 109 | font-size: 0.875rem; 110 | border-radius: 3px; 111 | box-sizing: border-box; 112 | } 113 | 114 | table { 115 | border-collapse: collapse; 116 | } 117 | 118 | .prose table th, 119 | .prose table td { 120 | text-align: left; 121 | padding: 8px; 122 | border: 1px solid #ddd; 123 | } 124 | 125 | .prose table th:nth-child(1) { 126 | border-right: none; 127 | } 128 | .prose table th:nth-child(2) { 129 | border-left: none; 130 | } 131 | 132 | .prose table { 133 | border: 1px solid #ddd; 134 | } 135 | 136 | .prose-big { 137 | font-size: 18px; 138 | line-height: 30px; 139 | } 140 | 141 | .quiet { 142 | opacity: 0.7; 143 | } 144 | 145 | .minishadow { 146 | box-shadow: 2px 2px 10px #f3f3f3; 147 | } 148 | -------------------------------------------------------------------------------- /src/default_theme/note._: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | <%- note.name %> 5 |

6 | 7 | <% if (note.description) { %> 8 | <%= md(note.description) %> 9 | <% } %> 10 |
-------------------------------------------------------------------------------- /src/default_theme/paramProperty._: -------------------------------------------------------------------------------- 1 | 2 | <%- property.name %> <%= formatType(property.type) %> 3 | <% if (property.default) { %> 4 | (default <%- property.default %>) 5 | <% } %> 6 | <%= md(property.description, true) %> 7 | 8 | <% if(property.properties && property.properties.length) { %> 9 | <% property.properties.forEach(function(childProperty) { %> 10 | <%= renderParamProperty({ 11 | property: childProperty, 12 | renderParamProperty: renderParamProperty 13 | }) %> 14 | <% }) %> 15 | <% } %> 16 | -------------------------------------------------------------------------------- /src/default_theme/section_list._: -------------------------------------------------------------------------------- 1 |
2 | <% members.forEach(function(member) { %> 3 |
4 |
5 |
6 | 7 | <%= shortSignature(member) %> 8 |
9 |
10 | 18 |
19 | <% }) %> 20 |
21 | -------------------------------------------------------------------------------- /src/extractors/comments.js: -------------------------------------------------------------------------------- 1 | import babelTraverse from '@babel/traverse'; 2 | import isJSDocComment from '../is_jsdoc_comment.js'; 3 | 4 | const traverse = babelTraverse.default || babelTraverse; 5 | 6 | /** 7 | * Iterate through the abstract syntax tree, finding a different kind of comment 8 | * each time, and optionally including context. This is how we find 9 | * JSDoc annotations that will become part of documentation 10 | * @param type comment type to find 11 | * @param includeContext to include context in the nodes 12 | * @param ast the babel-parsed syntax tree 13 | * @param data the filename and the source of the file the comment is in 14 | * @param addComment a method that creates a new comment if necessary 15 | * @returns comments 16 | * @private 17 | */ 18 | export default function walkComments( 19 | type, 20 | includeContext, 21 | ast, 22 | data, 23 | addComment 24 | ) { 25 | const newResults = []; 26 | 27 | traverse(ast, { 28 | /** 29 | * Process a parse in an abstract syntax tree 30 | * @param {Object} path ast path 31 | * @returns {undefined} causes side effects 32 | * @private 33 | */ 34 | enter(path) { 35 | /** 36 | * Parse a comment with doctrine and decorate the result with file position and code context. 37 | * 38 | * @param {Object} comment the current state of the parsed JSDoc comment 39 | * @returns {undefined} this emits data 40 | */ 41 | function parseComment(comment) { 42 | newResults.push( 43 | addComment( 44 | data, 45 | comment.value, 46 | comment.loc, 47 | path, 48 | path.node.loc, 49 | includeContext 50 | ) 51 | ); 52 | } 53 | 54 | (path.node[type] || []).filter(isJSDocComment).forEach(parseComment); 55 | } 56 | }); 57 | 58 | traverse.cache.clear(); 59 | 60 | return newResults; 61 | } 62 | -------------------------------------------------------------------------------- /src/filter_access.js: -------------------------------------------------------------------------------- 1 | import walk from './walk.js'; 2 | 3 | /** 4 | * Exclude given access levels from the generated documentation: this allows 5 | * users to write documentation for non-public members by using the 6 | * `@private` tag. 7 | * 8 | * @param {Array} [levels=['public', 'undefined', 'protected']] included access levels. 9 | * @param {Array} comments parsed comments (can be nested) 10 | * @returns {Array} filtered comments 11 | */ 12 | function filterAccess(levels, comments) { 13 | function filter(comment) { 14 | return ( 15 | comment.kind === 'note' || 16 | (!comment.ignore && levels.indexOf(String(comment.access)) !== -1) 17 | ); 18 | } 19 | 20 | function recurse(comment) { 21 | for (const scope in comment.members) { 22 | comment.members[scope] = comment.members[scope].filter(filter); 23 | } 24 | } 25 | 26 | return walk(comments.filter(filter), recurse); 27 | } 28 | 29 | export default filterAccess; 30 | -------------------------------------------------------------------------------- /src/garbage_collect.js: -------------------------------------------------------------------------------- 1 | function garbageCollect(comment) { 2 | delete comment.context.code; 3 | delete comment.context.ast; 4 | return comment; 5 | } 6 | 7 | export default garbageCollect; 8 | -------------------------------------------------------------------------------- /src/get-readme-file.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | export default function findReadme(dir) { 5 | const readmeFilenames = [ 6 | 'README.markdown', 7 | 'README.md', 8 | 'Readme.md', 9 | 'readme.markdown', 10 | 'readme.md' 11 | ]; 12 | 13 | const readmeFile = fs.readdirSync(dir).find(function (filename) { 14 | return readmeFilenames.indexOf(filename) >= 0; 15 | }); 16 | 17 | if (readmeFile) { 18 | return path.join(fs.realpathSync(dir), readmeFile); 19 | } 20 | 21 | return 'README.md'; 22 | } 23 | -------------------------------------------------------------------------------- /src/git/find_git.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | /** 5 | * Given a full path to a single file, iterate upwards through the filesystem 6 | * to find a directory with a .git file indicating that it is a git repository 7 | * @param filename any file within a repository 8 | * @returns repository root & its .git folder paths 9 | */ 10 | export default function findGit(filename) { 11 | let root = path.resolve(filename); 12 | while (root) { 13 | root = path.dirname(root); 14 | let git = path.join(root, '.git'); 15 | if (!fs.existsSync(git)) continue; 16 | 17 | if (fs.statSync(git).isFile()) { 18 | // git submodule 19 | const matches = fs.readFileSync(git, 'utf8').match(/gitdir: (.*)/); 20 | if (!matches) return null; 21 | git = path.join(root, matches[1]); 22 | } 23 | 24 | return { root, git }; 25 | } 26 | return null; 27 | } 28 | -------------------------------------------------------------------------------- /src/git/url_prefix.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import gitUrlParse from 'git-url-parse'; 4 | import ini from 'ini'; 5 | 6 | /** 7 | * Sometimes git will [pack refs](https://git-scm.com/docs/git-pack-refs) 8 | * in order to save space on disk and 9 | * duck under limits of numbers of files in folders. CircleCI in particular 10 | * does this by default. This method parses that `packed-refs` file 11 | * 12 | * @private 13 | * @param {string} packedRefs string contents of the packed refs file 14 | * @param {string} branchName the branch name to resolve to 15 | * @returns {string} sha hash referring to current tree 16 | */ 17 | export function parsePackedRefs(packedRefs, branchName) { 18 | return packedRefs 19 | .split(/\n/) 20 | .filter(line => line[0] !== '#' && line[0] !== '^') 21 | .reduce((memo, line) => { 22 | memo[line.split(' ')[1]] = line.split(' ')[0]; 23 | return memo; 24 | }, {})[branchName]; 25 | } 26 | 27 | /** 28 | * Given a a root directory, find its git configuration and figure out 29 | * the HTTPS URL at the base of that GitHub repository. 30 | * 31 | * @param {string} root path at the base of this local repo 32 | * @returns {string} base HTTPS url of the GitHub repository 33 | * @throws {Error} if the root is not a git repo 34 | */ 35 | export function getGithubURLPrefix({ git, root }) { 36 | let sha; 37 | try { 38 | const head = fs.readFileSync(path.join(git, 'HEAD'), 'utf8'); 39 | const branch = head.match(/ref: (.*)/); 40 | if (branch) { 41 | const branchName = branch[1]; 42 | const branchFileName = path.join(git, branchName); 43 | const packedRefsName = path.join(git, 'packed-refs'); 44 | if (fs.existsSync(branchFileName)) { 45 | sha = fs.readFileSync(branchFileName, 'utf8'); 46 | } else if (fs.existsSync(packedRefsName)) { 47 | // packed refs are a compacted version of the refs folder. usually 48 | // you have a folder filled with files that just contain sha 49 | // hashes. since this folder can be really big, packed refs 50 | // stores all the refs in one file instead. 51 | sha = parsePackedRefs( 52 | fs.readFileSync(packedRefsName, 'utf8'), 53 | branchName 54 | ); 55 | } 56 | } else { 57 | sha = head; 58 | } 59 | if (sha) { 60 | let origin; 61 | if (git.indexOf(root) === 0) { 62 | const config = parseGitConfig(path.join(git, 'config')); 63 | origin = config['remote "origin"'].url; 64 | } else { 65 | const config = parseGitConfig(path.join(git, '..', '..', 'config')); 66 | origin = config[`submodule "${path.basename(git)}"`].url; 67 | } 68 | const parsed = gitUrlParse(origin); 69 | parsed.git_suffix = false; // eslint-disable-line 70 | return parsed.toString('https') + '/blob/' + sha.trim() + '/'; 71 | } 72 | } catch (e) { 73 | return null; 74 | } 75 | } 76 | 77 | function parseGitConfig(configPath) { 78 | const str = fs 79 | .readFileSync(configPath, 'utf8') 80 | .replace( 81 | /\[(\S+) "(.+)"\]/g, 82 | (match, key, value) => `[${key} "${value.split('.').join('\\.')}"]` 83 | ); 84 | return ini.parse(str); 85 | } 86 | -------------------------------------------------------------------------------- /src/github.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import findGit from './git/find_git.js'; 3 | import { getGithubURLPrefix } from './git/url_prefix.js'; 4 | 5 | /** 6 | * Attempts to link code to its place on GitHub. 7 | * 8 | * @name linkGitHub 9 | * @param {Object} comment parsed comment 10 | * @returns {Object} comment with github inferred 11 | */ 12 | export default function (comment) { 13 | const paths = findGit(comment.context.file); 14 | 15 | const urlPrefix = paths && getGithubURLPrefix(paths); 16 | 17 | if (urlPrefix) { 18 | const fileRelativePath = comment.context.file 19 | .replace(paths.root + path.sep, '') 20 | .split(path.sep) 21 | .join('/'); 22 | 23 | let startLine; 24 | let endLine; 25 | 26 | if (comment.kind == 'typedef') { 27 | startLine = comment.loc.start.line; 28 | endLine = comment.loc.end.line; 29 | } else { 30 | startLine = comment.context.loc.start.line; 31 | endLine = comment.context.loc.end.line; 32 | } 33 | 34 | comment.context.github = { 35 | url: 36 | urlPrefix + fileRelativePath + '#L' + startLine + '-' + 'L' + endLine, 37 | path: fileRelativePath 38 | }; 39 | } 40 | return comment; 41 | } 42 | -------------------------------------------------------------------------------- /src/infer/access.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Given a string with a pattern that might infer access level, like `^_`, 3 | * create an inference method. 4 | * 5 | * @param {?string} pattern regexp-compatible pattern 6 | * @returns {Function} inference method 7 | * @private 8 | */ 9 | export default function inferAccessWithPattern(pattern) { 10 | const re = pattern && new RegExp(pattern); 11 | 12 | /** 13 | * Infers access from TypeScript annotations, and from the name (only private atm). 14 | * 15 | * @name inferAccess 16 | * @param {Object} comment parsed comment 17 | * @returns {Object} comment with access inferred 18 | */ 19 | return function inferAccess(comment) { 20 | // Support typescript access modifiers 21 | const ast = comment.context.ast; 22 | if (ast && ast.node.accessibility) { 23 | comment.access = ast.node.accessibility; 24 | } 25 | 26 | if (ast && ast.node.readonly) { 27 | comment.readonly = true; 28 | } 29 | 30 | // This needs to run after inferName because we infer the access based on 31 | // the name. 32 | if ( 33 | re && 34 | comment.name && 35 | comment.access === undefined && 36 | re.test(comment.name) 37 | ) { 38 | comment.access = 'private'; 39 | } 40 | 41 | return comment; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/infer/augments.js: -------------------------------------------------------------------------------- 1 | import babelGenerate from '@babel/generator'; 2 | import findTarget from './finders.js'; 3 | 4 | const generate = babelGenerate.default || babelGenerate; 5 | 6 | /** 7 | * Infers an `augments` tag from an ES6 class declaration 8 | * 9 | * @param {Object} comment parsed comment 10 | * @returns {Object} comment with kind inferred 11 | */ 12 | export default function inferAugments(comment) { 13 | if (comment.augments.length) { 14 | return comment; 15 | } 16 | 17 | const path = findTarget(comment.context.ast); 18 | 19 | if (!path) { 20 | return comment; 21 | } 22 | 23 | if (path.isClass()) { 24 | /* 25 | * A superclass can be a single name, like React, 26 | * or a MemberExpression like React.Component, 27 | * so we generate code from the AST rather than assuming 28 | * we can access a name like `path.node.superClass.name` 29 | */ 30 | if (path.node.superClass) { 31 | comment.augments.push({ 32 | title: 'augments', 33 | name: generate(path.node.superClass).code 34 | }); 35 | } 36 | } else if ( 37 | (path.isInterfaceDeclaration() || path.isTSInterfaceDeclaration()) && 38 | path.node.extends 39 | ) { 40 | /* 41 | * extends is an array of interface identifiers or 42 | * qualified type identifiers, so we generate code 43 | * from the AST rather than assuming we can acces 44 | * a name. 45 | */ 46 | path.node.extends.forEach(node => { 47 | comment.augments.push({ 48 | title: 'extends', 49 | name: generate(node).code 50 | }); 51 | }); 52 | } 53 | 54 | return comment; 55 | } 56 | -------------------------------------------------------------------------------- /src/infer/finders.js: -------------------------------------------------------------------------------- 1 | import t from '@babel/types'; 2 | 3 | /** 4 | * Try to find the part of JavaScript a comment is referring to, by 5 | * looking at the syntax tree closest to that comment. 6 | * 7 | * @param {Object} path abstract syntax tree path 8 | * @returns {?Object} ast path, if one is found. 9 | * @private 10 | */ 11 | export default function findTarget(path) { 12 | if (!path) { 13 | return path; 14 | } 15 | 16 | if ( 17 | t.isExportDefaultDeclaration(path) || 18 | (t.isExportNamedDeclaration(path) && path.has('declaration')) 19 | ) { 20 | path = path.get('declaration'); 21 | } 22 | 23 | if (t.isVariableDeclaration(path)) { 24 | // var x = init; 25 | path = path.get('declarations')[0]; 26 | } else if (t.isExpressionStatement(path)) { 27 | // foo.x = TARGET 28 | path = path.get('expression').get('right'); 29 | } else if (t.isObjectProperty(path) || t.isObjectTypeProperty(path)) { 30 | // var foo = { x: TARGET }; object property 31 | path = path.get('value'); 32 | } else if (t.isClassProperty(path) && path.get('value').node) { 33 | // var foo = { x = TARGET }; class property 34 | path = path.get('value'); 35 | } 36 | 37 | return path.node && path; 38 | } 39 | -------------------------------------------------------------------------------- /src/infer/implements.js: -------------------------------------------------------------------------------- 1 | import babelGenerate from '@babel/generator'; 2 | import findTarget from './finders.js'; 3 | 4 | const generate = babelGenerate.default || babelGenerate; 5 | 6 | /** 7 | * Infers an `augments` tag from an ES6 class declaration 8 | * 9 | * @param {Object} comment parsed comment 10 | * @returns {Object} comment with kind inferred 11 | */ 12 | export default function inferImplements(comment) { 13 | if (comment.implements.length) { 14 | return comment; 15 | } 16 | 17 | const path = findTarget(comment.context.ast); 18 | if (!path) { 19 | return comment; 20 | } 21 | 22 | if (path.isClass() && path.node.implements) { 23 | /* 24 | * A interface can be a single name, like React, 25 | * or a MemberExpression like React.Component, 26 | * so we generate code from the AST rather than assuming 27 | * we can access a name like `path.node.implements.name` 28 | */ 29 | path.node.implements.forEach(impl => { 30 | comment.implements.push({ 31 | title: 'implements', 32 | name: generate(impl).code 33 | }); 34 | }); 35 | } 36 | 37 | return comment; 38 | } 39 | -------------------------------------------------------------------------------- /src/infer/kind.js: -------------------------------------------------------------------------------- 1 | import t from '@babel/types'; 2 | 3 | /** 4 | * Infers a `kind` tag from the context. 5 | * 6 | * @param {Object} comment parsed comment 7 | * @returns {Object} comment with kind inferred 8 | */ 9 | export default function inferKind(comment) { 10 | if (comment.kind) { 11 | return comment; 12 | } 13 | 14 | function findKind(node) { 15 | if (!node) { 16 | return comment; 17 | } 18 | 19 | if (t.isClassDeclaration(node)) { 20 | comment.kind = 'class'; 21 | if (node.abstract) { 22 | comment.abstract = true; 23 | } 24 | } else if ( 25 | t.isFunction(node) || 26 | t.isTSDeclareMethod(node) || 27 | t.isTSDeclareFunction(node) || 28 | t.isFunctionTypeAnnotation(node) || 29 | t.isTSMethodSignature(node) 30 | ) { 31 | if (node.kind === 'get' || node.kind === 'set') { 32 | comment.kind = 'member'; 33 | } else if (node.id && node.id.name && !!/^[A-Z]/.exec(node.id.name)) { 34 | comment.kind = 'class'; 35 | } else { 36 | comment.kind = 'function'; 37 | if (node.async) { 38 | comment.async = true; 39 | } 40 | if (node.generator) { 41 | comment.generator = true; 42 | } 43 | if (node.abstract) { 44 | comment.abstract = true; 45 | } 46 | } 47 | } else if (t.isTypeAlias(node) || t.isTSTypeAliasDeclaration(node)) { 48 | comment.kind = 'typedef'; 49 | } else if ( 50 | t.isInterfaceDeclaration(node) || 51 | t.isTSInterfaceDeclaration(node) 52 | ) { 53 | comment.kind = 'interface'; 54 | } else if (t.isVariableDeclaration(node)) { 55 | if (node.kind === 'const') { 56 | comment.kind = 'constant'; 57 | } else { 58 | // This behavior is in need of fixing https://github.com/documentationjs/documentation/issues/351 59 | findKind(node.declarations[0].init); 60 | } 61 | } else if (t.isExportDeclaration(node)) { 62 | // export var x = ... 63 | // export function f() {} 64 | // export class C {} 65 | // export default function f() {} 66 | // export default class C {} 67 | findKind(node.declaration); 68 | } else if (t.isExpressionStatement(node)) { 69 | // module.exports = function() {} 70 | findKind(node.expression.right); 71 | } else if ( 72 | t.isClassProperty(node) || 73 | t.isTSPropertySignature(node) || 74 | t.isTSEnumMember(node) 75 | ) { 76 | comment.kind = 'member'; 77 | } else if (t.isProperty(node)) { 78 | // { foo: function() {} } 79 | findKind(node.value); 80 | } else if (t.isTSModuleDeclaration(node)) { 81 | comment.kind = 'namespace'; 82 | } else if (t.isObjectTypeProperty(node)) { 83 | if (t.isFunctionTypeAnnotation(node.value)) { 84 | findKind(node.value); 85 | } else { 86 | comment.kind = 'member'; 87 | } 88 | } else if (t.isTSEnumDeclaration(node)) { 89 | comment.kind = 'enum'; 90 | } 91 | } 92 | 93 | if (comment.context.ast) { 94 | findKind(comment.context.ast.node); 95 | } 96 | 97 | return comment; 98 | } 99 | -------------------------------------------------------------------------------- /src/infer/name.js: -------------------------------------------------------------------------------- 1 | import pathParse from 'parse-filepath'; 2 | import t from '@babel/types'; 3 | 4 | /** 5 | * Infers a `name` tag from the context. 6 | * 7 | * @name inferName 8 | * @param {Object} comment parsed comment 9 | * @returns {Object} comment with name inferred 10 | */ 11 | export default function inferName(comment) { 12 | if (comment.name) { 13 | return comment; 14 | } 15 | 16 | if (comment.alias) { 17 | comment.name = comment.alias; 18 | return comment; 19 | } 20 | 21 | if (comment.kind === 'module') { 22 | comment.name = pathParse(comment.context.file).name; 23 | return comment; 24 | } 25 | 26 | function inferName(path, node) { 27 | if (node && node.name) { 28 | comment.name = node.name; 29 | return true; 30 | } 31 | if (node && node.type === 'StringLiteral' && node.value) { 32 | comment.name = node.value; 33 | return true; 34 | } 35 | } 36 | 37 | const path = comment.context.ast; 38 | if (path) { 39 | if (path.type === 'ExportDefaultDeclaration') { 40 | if (t.isDeclaration(path.node.declaration) && path.node.declaration.id) { 41 | comment.name = path.node.declaration.id.name; 42 | } else { 43 | comment.name = pathParse(comment.context.file).name; 44 | } 45 | return comment; 46 | } 47 | 48 | // The strategy here is to do a depth-first traversal of the AST, 49 | // looking for nodes with a "name" property, with exceptions as needed. 50 | // For example, name inference for a MemberExpression `foo.bar = baz` will 51 | // infer the named based on the `property` of the MemberExpression (`bar`) 52 | // rather than the `object` (`foo`). 53 | path.traverse({ 54 | /** 55 | * Attempt to extract the name from an Identifier node. 56 | * If the name can be resolved, it will stop traversing. 57 | * @param {Object} path ast path 58 | * @returns {undefined} has side-effects 59 | * @private 60 | */ 61 | Identifier(path) { 62 | if (inferName(path, path.node)) { 63 | path.stop(); 64 | } 65 | }, 66 | /** 67 | * Attempt to extract the name from a string literal that is the `key` 68 | * part of an ObjectProperty node. If the name can be resolved, it 69 | * will stop traversing. 70 | * @param {Object} path ast path 71 | * @returns {undefined} has side-effects 72 | * @private 73 | */ 74 | StringLiteral(path) { 75 | if ( 76 | path.parent.type === 'ObjectProperty' && 77 | path.node === path.parent.key 78 | ) { 79 | if (inferName(path, path.node)) { 80 | path.stop(); 81 | } 82 | } 83 | }, 84 | /** 85 | * Attempt to extract the name from an Identifier node. 86 | * If the name can be resolved, it will stop traversing. 87 | * @param {Object} path ast path 88 | * @returns {undefined} has side-effects 89 | * @private 90 | */ 91 | MemberExpression(path) { 92 | if (inferName(path, path.node.property)) { 93 | path.stop(); 94 | } 95 | } 96 | }); 97 | } 98 | 99 | return comment; 100 | } 101 | -------------------------------------------------------------------------------- /src/infer/properties.js: -------------------------------------------------------------------------------- 1 | import typeAnnotation from '../type_annotation.js'; 2 | import findTarget from './finders.js'; 3 | 4 | function prefixedName(name, prefix) { 5 | if (prefix.length) { 6 | return prefix.join('.') + '.' + name; 7 | } 8 | return name; 9 | } 10 | 11 | function isObjectSpreadAndExactUtilTypeProperty(property) { 12 | return ( 13 | property.type === 'ObjectTypeSpreadProperty' && 14 | property.argument.id.name === '$Exact' 15 | ); 16 | } 17 | 18 | function propertyToDoc(property, prefix) { 19 | let type; 20 | let name; 21 | 22 | if (property.type === 'ObjectTypeProperty') { 23 | // flow 24 | type = typeAnnotation(property.value); 25 | } else if (property.type === 'TSPropertySignature') { 26 | // typescript 27 | type = typeAnnotation(property.typeAnnotation); 28 | } else if (property.type === 'TSMethodSignature') { 29 | // typescript 30 | type = typeAnnotation(property); 31 | } 32 | 33 | if (property.key) { 34 | name = property.key.name || property.key.value; 35 | } 36 | 37 | // Special handling for { ...$Exact } 38 | if (isObjectSpreadAndExactUtilTypeProperty(property)) { 39 | name = property.argument.id.name; 40 | type = { 41 | type: 'NameExpression', 42 | name: property.argument.typeParameters.params[0].id.name 43 | }; 44 | } 45 | 46 | if (property.optional) { 47 | type = { 48 | type: 'OptionalType', 49 | expression: type 50 | }; 51 | } 52 | return { 53 | title: 'property', 54 | name: prefixedName(name, prefix), 55 | lineNumber: property.loc.start.line, 56 | type 57 | }; 58 | } 59 | 60 | /** 61 | * Infers properties of TypeAlias objects (Flow or TypeScript type definitions) 62 | * 63 | * @param {Object} comment parsed comment 64 | * @returns {Object} comment with inferred properties 65 | */ 66 | export default function inferProperties(comment) { 67 | const explicitProperties = new Set(); 68 | // Ensure that explicitly specified properties are not overridden 69 | // by inferred properties 70 | comment.properties.forEach(prop => explicitProperties.add(prop.name)); 71 | 72 | function inferProperties(value, prefix) { 73 | if ( 74 | value.type === 'ObjectTypeAnnotation' || 75 | value.type === 'TSTypeLiteral' 76 | ) { 77 | const properties = value.properties || value.members || value.body || []; 78 | properties.forEach(function (property) { 79 | let name; 80 | 81 | if (property.key) { 82 | name = property.key.name; 83 | } 84 | 85 | // Special handling for { ...$Exact } 86 | if (isObjectSpreadAndExactUtilTypeProperty(property)) { 87 | name = property.argument.id.name; 88 | } 89 | 90 | if (!explicitProperties.has(prefixedName(name, prefix))) { 91 | comment.properties = comment.properties.concat( 92 | propertyToDoc(property, prefix) 93 | ); 94 | } 95 | }); 96 | } 97 | } 98 | 99 | const path = findTarget(comment.context.ast); 100 | 101 | if (path) { 102 | if (path.isTypeAlias()) { 103 | inferProperties(path.node.right, []); 104 | } else if (path.isTSTypeAliasDeclaration()) { 105 | inferProperties(path.node.typeAnnotation, []); 106 | } 107 | } 108 | 109 | return comment; 110 | } 111 | -------------------------------------------------------------------------------- /src/infer/return.js: -------------------------------------------------------------------------------- 1 | import findTarget from './finders.js'; 2 | import t from '@babel/types'; 3 | import typeAnnotation from '../type_annotation.js'; 4 | 5 | // TypeScript does not currently support typing the return value of a generator function. 6 | // This is coming in TypeScript 3.3 - https://github.com/Microsoft/TypeScript/pull/30790 7 | const TS_GENERATORS = { 8 | Iterator: 1, 9 | Iterable: 1, 10 | IterableIterator: 1 11 | }; 12 | 13 | const FLOW_GENERATORS = { 14 | Iterator: 1, 15 | Iterable: 1, 16 | Generator: 3 17 | }; 18 | 19 | /** 20 | * Infers returns tags by using Flow return type annotations 21 | * 22 | * @name inferReturn 23 | * @param {Object} comment parsed comment 24 | * @returns {Object} comment with return tag inferred 25 | */ 26 | export default function inferReturn(comment) { 27 | if ( 28 | Array.isArray(comment.returns) && 29 | comment.returns.length && 30 | comment.returns[0].type 31 | ) { 32 | return comment; 33 | } 34 | const path = findTarget(comment.context.ast); 35 | let fn = path && path.node; 36 | if (!fn) { 37 | return comment; 38 | } 39 | 40 | // In case of `/** */ var x = function () {}` findTarget returns 41 | // the declarator. 42 | if (t.isVariableDeclarator(fn)) { 43 | fn = fn.init; 44 | } 45 | 46 | const fnReturnType = getReturnType(fn); 47 | if (fnReturnType) { 48 | let returnType = typeAnnotation(fnReturnType); 49 | let yieldsType = null; 50 | 51 | if (fn.generator && returnType.type === 'TypeApplication') { 52 | comment.generator = true; 53 | let numArgs; 54 | 55 | if (t.isFlow(fnReturnType)) { 56 | numArgs = FLOW_GENERATORS[returnType.expression.name]; 57 | } else if (t.isTSTypeAnnotation(fnReturnType)) { 58 | numArgs = TS_GENERATORS[returnType.expression.name]; 59 | } 60 | 61 | if (returnType.applications.length === numArgs) { 62 | yieldsType = returnType.applications[0]; 63 | 64 | if (numArgs > 1) { 65 | returnType = returnType.applications[1]; 66 | } else { 67 | returnType = { 68 | type: 'VoidLiteral' 69 | }; 70 | } 71 | } 72 | } 73 | 74 | if (yieldsType) { 75 | if (comment.yields && comment.yields.length > 0) { 76 | comment.yields[0].type = yieldsType; 77 | } else { 78 | comment.yields = [ 79 | { 80 | title: 'yields', 81 | type: yieldsType 82 | } 83 | ]; 84 | } 85 | } 86 | 87 | if (comment.returns && comment.returns.length > 0) { 88 | comment.returns[0].type = returnType; 89 | } else { 90 | comment.returns = [ 91 | { 92 | title: 'returns', 93 | type: returnType 94 | } 95 | ]; 96 | } 97 | } 98 | return comment; 99 | } 100 | 101 | function getReturnType(fn) { 102 | if ( 103 | t.isFunction(fn) || 104 | t.isTSDeclareFunction(fn) || 105 | t.isTSDeclareMethod(fn) || 106 | t.isFunctionTypeAnnotation(fn) 107 | ) { 108 | return fn.returnType; 109 | } 110 | 111 | if (t.isTSMethodSignature(fn)) { 112 | return fn.typeAnnotation; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/infer/type.js: -------------------------------------------------------------------------------- 1 | import findTarget from './finders.js'; 2 | import typeAnnotation from '../type_annotation.js'; 3 | 4 | const constTypeMapping = { 5 | BooleanLiteral: { type: 'BooleanTypeAnnotation' }, 6 | NumericLiteral: { type: 'NumberTypeAnnotation' }, 7 | StringLiteral: { type: 'StringTypeAnnotation' } 8 | }; 9 | 10 | /** 11 | * Infers type tags by using Flow/TypeScript type annotations 12 | * 13 | * @name inferType 14 | * @param {Object} comment parsed comment 15 | * @returns {Object} comment with type tag inferred 16 | */ 17 | export default function inferType(comment) { 18 | if (comment.type) { 19 | return comment; 20 | } 21 | 22 | const ast = comment.context.ast; 23 | const path = findTarget(ast); 24 | if (!path) { 25 | return comment; 26 | } 27 | 28 | const n = path.node; 29 | let type; 30 | switch (n.type) { 31 | case 'VariableDeclarator': 32 | type = n.id.typeAnnotation; 33 | if (!type && comment.kind === 'constant') { 34 | type = constTypeMapping[n.init.type]; 35 | } 36 | break; 37 | case 'ClassProperty': 38 | case 'TSTypeAliasDeclaration': 39 | case 'TSPropertySignature': 40 | type = n.typeAnnotation; 41 | break; 42 | case 'ClassMethod': 43 | case 'TSDeclareMethod': 44 | if (n.kind === 'get') { 45 | type = n.returnType; 46 | } else if (n.kind === 'set' && n.params[0]) { 47 | type = n.params[0].typeAnnotation; 48 | } 49 | break; 50 | case 'TypeAlias': 51 | type = n.right; 52 | break; 53 | case 'TSEnumMember': 54 | if (n.initializer) { 55 | if (constTypeMapping[n.initializer.type]) { 56 | type = constTypeMapping[n.initializer.type]; 57 | } 58 | } else { 59 | type = constTypeMapping.NumericLiteral; 60 | } 61 | break; 62 | default: 63 | if (ast.isObjectTypeProperty() && !ast.node.method) { 64 | type = ast.node.value; 65 | } 66 | } 67 | // Don't provide a `type` section when it's an ObjectTypeAnnotation, 68 | // `properties` already exists and renders better. 69 | if (type && type.type !== 'ObjectTypeAnnotation') { 70 | comment.type = typeAnnotation(type); 71 | } 72 | return comment; 73 | } 74 | -------------------------------------------------------------------------------- /src/input/dependency.js: -------------------------------------------------------------------------------- 1 | import mdeps from './moduleDeps.js'; 2 | import internalOnly from '../module_filters.js'; 3 | import smartGlob from './smart_glob.js'; 4 | 5 | /** 6 | * Returns a array of dependencies, given an array of entry 7 | * points and an object of options to provide to module-deps. 8 | * 9 | * This stream requires filesystem access, and thus isn't suitable 10 | * for a browser environment. 11 | * 12 | * @param indexes paths to entry files as strings 13 | * @param config optional options passed 14 | * @returns results 15 | */ 16 | export default async function dependencyStream( 17 | indexes, 18 | { parseExtension = [], requireExtension = [] } 19 | ) { 20 | const md = await mdeps(smartGlob(indexes, parseExtension), { 21 | /** 22 | * Determine whether a module should be included in documentation 23 | * @param {string} id path to a module 24 | * @returns {boolean} true if the module should be included. 25 | */ 26 | filter: id => internalOnly(id), 27 | extensions: [...parseExtension, ...requireExtension] 28 | }); 29 | 30 | return md; 31 | } 32 | -------------------------------------------------------------------------------- /src/input/moduleDeps.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import util from 'util'; 3 | import r from 'resolve'; 4 | import readFileCode from './readFileCode.js'; 5 | import konan from 'konan'; 6 | import { parseToAst } from '../parsers/parse_to_ast.js'; 7 | 8 | // const parseExst = ['.js', '.mjs', '.jsx', '.vue', '.ts', '.tsx']; 9 | const resolveExst = ['.json', '.css', '.less', '.sass']; 10 | const resolve = util.promisify(r); 11 | 12 | class Deps { 13 | constructor(opts = {}) { 14 | this.fileCache = opts.fileCache || {}; 15 | this.visited = {}; 16 | this.res = []; 17 | 18 | this.options = { ...opts }; 19 | } 20 | 21 | async flush(input) { 22 | const promises = input.map(file => { 23 | const dir = path.dirname(file); 24 | return this.walk(file, { 25 | basedir: dir, 26 | filename: 'root' 27 | }); 28 | }); 29 | await Promise.all(promises); 30 | 31 | return this.res; 32 | } 33 | 34 | async readFile(file) { 35 | if (this.fileCache[file]) { 36 | return this.fileCache[file]; 37 | } 38 | 39 | return readFileCode(file); 40 | } 41 | 42 | async walk(id, parent) { 43 | const extensions = this.options.extensions; 44 | const sortKey = parent.sortKey || ''; 45 | let file = null; 46 | 47 | try { 48 | file = await resolve(id, { ...parent, extensions }); 49 | } catch (err) { 50 | if (err.code === 'MODULE_NOT_FOUND') { 51 | console.warn(`module not found: "${id}" from file ${parent.filename}`); 52 | return; 53 | } 54 | throw err; 55 | } 56 | 57 | if (this.visited[file] || resolveExst.includes(path.extname(file))) { 58 | return file; 59 | } 60 | this.visited[file] = true; 61 | 62 | const source = await this.readFile(file); 63 | const depsArray = this.parseDeps(file, source); 64 | if (!depsArray) { 65 | return file; 66 | } 67 | 68 | const deps = {}; 69 | const promises = depsArray.map(async (id, i) => { 70 | const filter = this.options.filter; 71 | if (filter && !filter(id)) { 72 | deps[id] = false; 73 | return; 74 | } 75 | const number = i.toString().padStart(8, '0'); 76 | deps[id] = await this.walk(id, { 77 | filename: file, 78 | basedir: path.dirname(file), 79 | sortKey: sortKey + '!' + file + ':' + number 80 | }); 81 | }); 82 | 83 | await Promise.all(promises); 84 | 85 | this.res.push({ 86 | id: file, 87 | source, 88 | deps, 89 | file, 90 | sortKey: sortKey + '!' + file 91 | }); 92 | return file; 93 | } 94 | 95 | parseDeps(file, src) { 96 | try { 97 | const ast = parseToAst(src, file); 98 | return konan(ast).strings; 99 | } catch (ex) { 100 | console.error(`Parsing file ${file}: ${ex}`); 101 | } 102 | } 103 | } 104 | 105 | export default async function (input = [], opts = {}) { 106 | const dep = new Deps(opts); 107 | return dep.flush(Array.from(new Set(input))); 108 | } 109 | -------------------------------------------------------------------------------- /src/input/readFileCode.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { readFile } from 'fs/promises'; 3 | let vuecompiler = null; 4 | let vueVersion = 'v3'; 5 | 6 | async function vueParser(source) { 7 | if (!vuecompiler) { 8 | try { 9 | vuecompiler = await import('@vue/compiler-sfc'); 10 | } catch { 11 | try { 12 | vuecompiler = await import('vue-template-compiler'); 13 | vueVersion = 'v2'; 14 | } catch (err) { 15 | console.error( 16 | 'You need to load package vue-template-compiler for Vue 2 or @vue/compiler-sfc for Vue 3' 17 | ); 18 | throw err; 19 | } 20 | } 21 | } 22 | 23 | let component = {}; 24 | if (vueVersion === 'v3') { 25 | component = vuecompiler.parse(source).descriptor; 26 | } else { 27 | component = vuecompiler.parseComponent(source); 28 | } 29 | 30 | return component.script?.content || ''; 31 | } 32 | 33 | export default async function readFileCode(file) { 34 | let source = await readFile(file, { 35 | encoding: 'utf8' 36 | }); 37 | 38 | if (path.extname(file) === '.vue') { 39 | source = await vueParser(source); 40 | } 41 | return source; 42 | } 43 | -------------------------------------------------------------------------------- /src/input/shallow.js: -------------------------------------------------------------------------------- 1 | import smartGlob from './smart_glob.js'; 2 | import readFileCode from './readFileCode.js'; 3 | 4 | /** 5 | * A readable source for content that doesn't do dependency resolution, but 6 | * simply reads files and pushes them onto a stream. 7 | * 8 | * If an array of strings is provided as input to this method, then 9 | * they will be treated as filenames and read into the stream. 10 | * 11 | * If an array of objects is provided, then we assume that they are valid 12 | * objects with `source` and `file` properties, and don't use the filesystem 13 | * at all. This is one way of getting documentation.js to run in a browser 14 | * or without fs access. 15 | * 16 | * @param indexes entry points 17 | * @param config parsing options 18 | * @returns promise with parsed files 19 | */ 20 | export default async function (indexes, config) { 21 | const objects = []; 22 | const paths = indexes.filter(v => { 23 | if (typeof v === 'object') { 24 | v.file = v.file ?? ''; 25 | objects.push(v); 26 | return false; 27 | } 28 | return typeof v === 'string'; 29 | }); 30 | const files = await Promise.all( 31 | smartGlob(paths, config.parseExtension).map(async file => ({ 32 | source: await readFileCode(file), 33 | file 34 | })) 35 | ); 36 | 37 | return [...objects, ...files]; 38 | } 39 | -------------------------------------------------------------------------------- /src/is_jsdoc_comment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Detect whether a comment is a JSDoc comment: it must be a block 3 | * comment which starts with two asterisks, not any other number of asterisks. 4 | * 5 | * The code parser automatically strips out the first asterisk that's 6 | * required for the comment to be a comment at all, so we count the remaining 7 | * comments. 8 | * 9 | * @name isJSDocComment 10 | * @param {Object} comment an ast path of the comment 11 | * @returns {boolean} whether it is valid 12 | */ 13 | export default function isJSDocComment( 14 | comment /*: { 15 | value: string, 16 | type: string 17 | }*/ 18 | ) { 19 | const asterisks = comment.value.match(/^(\*+)/); 20 | return ( 21 | comment.type === 'CommentBlock' && asterisks && asterisks[1].length === 1 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/lint.js: -------------------------------------------------------------------------------- 1 | import { VFile } from 'vfile'; 2 | import walk from './walk.js'; 3 | import { sort as vfileSort } from 'vfile-sort'; 4 | import { reporter } from 'vfile-reporter'; 5 | import { nest } from './nest.js'; 6 | 7 | const CANONICAL = { 8 | String: 'string', 9 | Boolean: 'boolean', 10 | Undefined: 'undefined', 11 | Number: 'number', 12 | array: 'Array', 13 | date: 'Date', 14 | object: 'Object' 15 | }; 16 | 17 | /** 18 | * Passively lints and checks documentation data. 19 | * 20 | * @name lint 21 | * @param {Object} comment parsed comment 22 | * @returns {Array} array of errors 23 | */ 24 | export function lintComments(comment) { 25 | comment.tags.forEach(function (tag) { 26 | function nameInvariant(name) { 27 | if (name && typeof CANONICAL[name] === 'string') { 28 | comment.errors.push({ 29 | message: 30 | 'type ' + name + ' found, ' + CANONICAL[name] + ' is standard', 31 | commentLineNumber: tag.lineNumber 32 | }); 33 | } 34 | } 35 | 36 | function checkCanonical(type) { 37 | if (type.type === 'NameExpression') { 38 | nameInvariant(type.name); 39 | } 40 | 41 | if (type.elements) { 42 | checkSubtypes(type.elements); 43 | } 44 | if (type.applications) { 45 | checkSubtypes(type.applications); 46 | } 47 | } 48 | 49 | function checkSubtypes(subtypes) { 50 | if (Array.isArray(subtypes)) { 51 | subtypes.forEach(checkCanonical); 52 | } 53 | } 54 | 55 | if (tag.type && typeof tag.type === 'object') { 56 | checkCanonical(tag.type); 57 | } 58 | }); 59 | nest(comment); 60 | 61 | return comment; 62 | } 63 | 64 | /** 65 | * @private 66 | * Extract lint instructions from comments and generate user-readable output. 67 | * @param {Array} comments a list of comments 68 | * @returns {string} user-readable output 69 | */ 70 | export function formatLint(comments) { 71 | const vFiles = {}; 72 | walk(comments, function (comment) { 73 | comment.errors.forEach(function (error) { 74 | const p = comment.context.file; 75 | vFiles[p] = 76 | vFiles[p] || 77 | new VFile({ 78 | path: p 79 | }); 80 | vFiles[p].message(error.message, { 81 | line: comment.loc.start.line + (error.commentLineNumber || 0) 82 | }); 83 | }); 84 | }); 85 | return reporter(Object.keys(vFiles).map(p => vfileSort(vFiles[p]))); 86 | } 87 | -------------------------------------------------------------------------------- /src/merge_config.js: -------------------------------------------------------------------------------- 1 | import conf from './config.js'; 2 | import yaml from 'js-yaml'; 3 | import fs from 'fs'; 4 | import pify from 'pify'; 5 | import { readPackageUp } from 'read-pkg-up'; 6 | import path from 'path'; 7 | import stripComments from 'strip-json-comments'; 8 | 9 | function normalizeToc(config, basePath) { 10 | if (!config || !config.toc) { 11 | return config; 12 | } 13 | 14 | config.toc = config.toc.map(entry => { 15 | if (entry && entry.file) { 16 | entry.file = path.join(basePath, entry.file); 17 | } 18 | return entry; 19 | }); 20 | 21 | return config; 22 | } 23 | 24 | /** 25 | * Use the nearest package.json file for the default 26 | * values of `name` and `version` config. 27 | * 28 | * @param {boolean} noPackage options which prevent ge info about project from package.json 29 | * @returns {Promise} configuration with inferred parameters 30 | */ 31 | async function readPackage(noPackage) { 32 | const global = conf.globalConfig; 33 | if (noPackage) { 34 | return {}; 35 | } 36 | const param = ['name', 'homepage', 'version', 'description']; 37 | try { 38 | const { packageJson } = await readPackageUp(); 39 | return param.reduce((res, key) => { 40 | res[`project-${key}`] = global[key] || packageJson[key]; 41 | return res; 42 | }, {}); 43 | } catch (e) { 44 | return {}; 45 | } 46 | } 47 | 48 | /** 49 | * Merge a configuration file into program config, assuming that the location 50 | * of the configuration file is given as one of those config. 51 | * 52 | * @param {String} config the user-provided config path, usually via argv 53 | * @returns {Promise} configuration, which are parsed 54 | * @throws {Error} if the file cannot be read. 55 | */ 56 | async function readConfigFile(config) { 57 | if (typeof config !== 'string') { 58 | return {}; 59 | } 60 | const filePath = config; 61 | const absFilePath = path.resolve(process.cwd(), filePath); 62 | const rawFile = await pify(fs).readFile(absFilePath, 'utf8'); 63 | const basePath = path.dirname(absFilePath); 64 | 65 | let obj = null; 66 | if (path.extname(filePath) === '.json') { 67 | obj = JSON.parse(stripComments(rawFile)); 68 | } else { 69 | obj = yaml.load(rawFile); 70 | } 71 | if ('noPackage' in obj) { 72 | obj['no-package'] = obj.noPackage; 73 | delete obj.noPackage; 74 | } 75 | return normalizeToc(obj, basePath); 76 | } 77 | 78 | export default async function mergeConfig(config = {}) { 79 | conf.add(config); 80 | conf.add(await readConfigFile(conf.globalConfig.config)); 81 | conf.add(await readPackage(conf.globalConfig['no-package'])); 82 | 83 | return conf.globalConfig; 84 | } 85 | -------------------------------------------------------------------------------- /src/module_filters.js: -------------------------------------------------------------------------------- 1 | // Skip external modules. Based on http://git.io/pzPO. 2 | const internalModuleRegexp = 3 | process.platform === 'win32' 4 | ? /* istanbul ignore next */ 5 | /^(\.|\w:)/ 6 | : /^[/.]/; 7 | 8 | /** 9 | * Module filters 10 | */ 11 | export default id => internalModuleRegexp.test(id); 12 | -------------------------------------------------------------------------------- /src/nest.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | const PATH_SPLIT = /(?:\[])?\./g; 4 | 5 | function removeUnnamedTags(tags) { 6 | return tags.filter(tag => typeof tag.name === 'string'); 7 | } 8 | 9 | const tagDepth = tag => tag.name.split(PATH_SPLIT).length; 10 | 11 | /** 12 | * Nest nestable tags, like param and property, into nested 13 | * arrays that are suitable for output. 14 | * Okay, so we're building a tree of comments, with the tag.name 15 | * being the indexer. We sort by depth, so that we add each 16 | * level of the tree incrementally, and throw if we run against 17 | * a node that doesn't have a parent. 18 | * 19 | * foo.abe 20 | * foo.bar.baz 21 | * foo.bar.a 22 | * foo.bar[].bax 23 | * 24 | * foo -> .abe 25 | * \-> .bar -> .baz 26 | * \-> .a 27 | * \-> [].baz 28 | * 29 | * @private 30 | * @param {Array} tags a list of tags 31 | * @returns {Object} nested comment 32 | */ 33 | export const nestTag = ( 34 | tags, 35 | errors 36 | // Use lodash here both for brevity and also because, unlike JavaScript's 37 | // sort, it's stable. 38 | ) => 39 | _.sortBy(removeUnnamedTags(tags), tagDepth).reduce( 40 | (memo, tag) => { 41 | function insertTag(node, parts) { 42 | // The 'done' case: we're at parts.length === 1, 43 | // this is where the node should be placed. We reliably 44 | // get to this case because the recursive method 45 | // is always passed parts.slice(1) 46 | if (parts.length === 1) { 47 | Object.assign(node, { 48 | properties: (node.properties || []).concat(tag) 49 | }); 50 | } else { 51 | // The recursive case: try to find the child that owns 52 | // this tag. 53 | const child = 54 | node.properties && 55 | node.properties.find( 56 | property => parts[0] === _.last(property.name.split(PATH_SPLIT)) 57 | ); 58 | 59 | if (!child) { 60 | if (tag.name.match(/^(\$\d+)/)) { 61 | errors.push({ 62 | message: 63 | `Parent of ${tag.name} not found. To document a destructuring\n` + 64 | `type, add a @param tag in its position to specify the name of the\n` + 65 | `destructured parameter`, 66 | commentLineNumber: tag.lineNumber 67 | }); 68 | } else { 69 | errors.push({ 70 | message: `Parent of ${tag.name} not found`, 71 | commentLineNumber: tag.lineNumber 72 | }); 73 | } 74 | } else { 75 | insertTag(child, parts.slice(1)); 76 | } 77 | } 78 | } 79 | 80 | insertTag(memo, tag.name.split(PATH_SPLIT)); 81 | return memo; 82 | }, 83 | { properties: [] } 84 | ).properties; 85 | 86 | /** 87 | * Nests 88 | * [parameters with properties](http://usejsdoc.org/tags-param.html#parameters-with-properties). 89 | * 90 | * A parameter `employee.name` will be attached to the parent parameter `employee` in 91 | * a `properties` array. 92 | * 93 | * This assumes that incoming comments have been flattened. 94 | * 95 | * @param {Object} comment input comment 96 | * @returns {Object} nested comment 97 | */ 98 | export const nest = comment => 99 | Object.assign(comment, { 100 | params: nestTag(comment.params, comment.errors), 101 | properties: nestTag(comment.properties, comment.errors) 102 | }); 103 | -------------------------------------------------------------------------------- /src/output/highlighter.js: -------------------------------------------------------------------------------- 1 | import { visit } from 'unist-util-visit'; 2 | import hljs from 'highlight.js'; 3 | 4 | /** 5 | * Adapted from remark-highlight.js 6 | * https://github.com/ben-eb/remark-highlight.js 7 | * @param {Object} node AST node 8 | * @returns {undefined} modifies the node by reference 9 | * @private 10 | */ 11 | function visitor(node) { 12 | if (node.lang) { 13 | node.type = 'html'; 14 | node.value = 15 | "
" +
16 |       hljs.highlightAuto(node.value, [node.lang]).value +
17 |       '
'; 18 | } 19 | } 20 | 21 | export default function (ast) { 22 | visit(ast, 'code', visitor); 23 | return ast; 24 | } 25 | -------------------------------------------------------------------------------- /src/output/html.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import mergeConfig from '../merge_config.js'; 3 | 4 | /** 5 | * Formats documentation as HTML. 6 | * 7 | * @param {Array} comments parsed comments 8 | * @param {Object} config Options that can customize the output 9 | * @param {string} [config.theme='default_theme'] Name of a module used for an HTML theme. 10 | * @returns {Promise>} Promise with results 11 | * @name formats.html 12 | * @public 13 | * @example 14 | * var documentation = require('documentation'); 15 | * 16 | * documentation.build(['index.js']) 17 | * .then(documentation.formats.html); 18 | */ 19 | export default async function html(comments, localConfig = {}) { 20 | const config = await mergeConfig(localConfig); 21 | let themePath = config.theme && path.resolve(process.cwd(), config.theme); 22 | if (themePath) { 23 | if (process.platform === 'win32'){ 24 | // On Windows, absolute paths must be prefixed with 'file:///' to avoid the ERR_UNSUPPORTED_ESM_URL_SCHEME error from import(). 25 | themePath = 'file:///' + themePath; 26 | } 27 | 28 | return (await import(themePath)).default(comments, config); 29 | } 30 | return (await import('../default_theme/index.js')).default(comments, config); 31 | } 32 | -------------------------------------------------------------------------------- /src/output/json.js: -------------------------------------------------------------------------------- 1 | import walk from '../walk.js'; 2 | 3 | /** 4 | * Formats documentation as a JSON string. 5 | * 6 | * @param {Array} comments parsed comments 7 | * @returns {Promise} 8 | * @name formats.json 9 | * @public 10 | * @example 11 | * var documentation = require('documentation'); 12 | * var fs = require('fs'); 13 | * 14 | * documentation.build(['index.js']) 15 | * .then(documentation.formats.json) 16 | * .then(output => { 17 | * // output is a string of JSON data 18 | * fs.writeFileSync('./output.json', output); 19 | * }); 20 | */ 21 | export default function json(comments) { 22 | walk(comments, comment => { 23 | delete comment.errors; 24 | if (comment.context) { 25 | delete comment.context.sortKey; 26 | } 27 | }); 28 | 29 | return Promise.resolve(JSON.stringify(comments, null, 2)); 30 | } 31 | -------------------------------------------------------------------------------- /src/output/markdown.js: -------------------------------------------------------------------------------- 1 | import { remark } from 'remark'; 2 | import remarkGfm from 'remark-gfm'; 3 | import markdownAST from './markdown_ast.js'; 4 | 5 | /** 6 | * Formats documentation as 7 | * [Markdown](https://daringfireball.net/projects/markdown/). 8 | * 9 | * @param {Array} comments parsed comments 10 | * @param {Object} args Options that can customize the output 11 | * @name formats.markdown 12 | * @returns {Promise} a promise of the eventual value 13 | * @public 14 | * @example 15 | * var documentation = require('documentation'); 16 | * var fs = require('fs'); 17 | * 18 | * documentation.build(['index.js']) 19 | * .then(documentation.formats.md) 20 | * .then(output => { 21 | * // output is a string of Markdown data 22 | * fs.writeFileSync('./output.md', output); 23 | * }); 24 | */ 25 | export default function markdown(comments, args) { 26 | if (!args) { 27 | args = {}; 28 | } 29 | return markdownAST(comments, args).then(ast => 30 | remark().use(remarkGfm).stringify(ast) 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/output/util/formatters.js: -------------------------------------------------------------------------------- 1 | import { remark } from 'remark'; 2 | import html from 'remark-html'; 3 | import doctrine from 'doctrine-temporary-fork'; 4 | const Syntax = doctrine.Syntax; 5 | import { u } from 'unist-builder'; 6 | import _rerouteLinks from './reroute_links.js'; 7 | import highlighter from '../highlighter.js'; 8 | import formatType from './format_type.js'; 9 | 10 | /** 11 | * Create a formatter group, given a linker method that resolves 12 | * namespaces to URLs 13 | * 14 | * @param getHref linker method 15 | * @returns {formatters} formatter object 16 | */ 17 | export default function (getHref) { 18 | const rerouteLinks = _rerouteLinks.bind(undefined, getHref); 19 | 20 | const formatters = {}; 21 | 22 | /** 23 | * Format a parameter name. This is used in formatParameters 24 | * and just needs to be careful about differentiating optional 25 | * parameters 26 | * 27 | * @param {Object} param a param as a type spec 28 | * @param {boolean} short whether to cut the details and make it skimmable 29 | * @returns {string} formatted parameter representation. 30 | */ 31 | formatters.parameter = function (param, short) { 32 | if (short) { 33 | if (param.type && param.type.type == Syntax.OptionalType) { 34 | if (param.default) { 35 | return param.name + ' = ' + param.default; 36 | } 37 | return param.name + '?'; 38 | } 39 | return param.name; 40 | } 41 | return param.name + ': ' + formatters.type(param.type).replace(/\n/g, ''); 42 | }; 43 | 44 | /** 45 | * Convert a remark AST to a string of HTML, rerouting links as necessary 46 | * @param {Object} ast remark-compatible AST 47 | * @returns {string} HTML 48 | */ 49 | formatters.markdown = function (ast) { 50 | if (ast) { 51 | return remark() 52 | .use(html) 53 | .stringify(highlighter(rerouteLinks(ast))); 54 | } 55 | return ''; 56 | }; 57 | 58 | /** 59 | * Format a type and convert it to HTML 60 | * 61 | * @param {Object} type doctrine-format type 62 | * @returns {string} HTML 63 | */ 64 | formatters.type = function (type) { 65 | return formatters 66 | .markdown(u('root', formatType(getHref, type))) 67 | .replace(/\n/g, ''); 68 | }; 69 | 70 | /** 71 | * Link text to this page or to a central resource. 72 | * @param {string} text inner text of the link 73 | * @returns {string} potentially linked HTML 74 | */ 75 | formatters.autolink = function (text) { 76 | const href = getHref(text); 77 | if (href) { 78 | // TODO: this is a temporary fix until we drop remark 3.x support, 79 | // and then we should remove the 'href' property and only 80 | // have the url property of links 81 | return formatters 82 | .markdown( 83 | u( 84 | 'link', 85 | { 86 | href, 87 | url: href 88 | }, 89 | [u('text', text)] 90 | ) 91 | ) 92 | .replace(/\n/g, ''); 93 | } 94 | return formatters.markdown(u('text', text)).replace(/\n/g, ''); 95 | }; 96 | 97 | /** 98 | * Format the parameters of a function into a quickly-readable 99 | * summary that resembles how you would call the function 100 | * initially. 101 | * 102 | * @param {Object} section comment node from documentation 103 | * @param {boolean} short whether to cut the details and make it skimmable 104 | * @returns {string} formatted parameters 105 | */ 106 | formatters.parameters = function (section, short) { 107 | if (section.params) { 108 | return ( 109 | '(' + 110 | section.params 111 | .map(function (param) { 112 | return formatters.parameter(param, short); 113 | }) 114 | .join(', ') + 115 | ')' 116 | ); 117 | } 118 | return '()'; 119 | }; 120 | 121 | return formatters; 122 | } 123 | -------------------------------------------------------------------------------- /src/output/util/linker_stack.js: -------------------------------------------------------------------------------- 1 | import globalsDocs from 'globals-docs'; 2 | import walk from '../../walk.js'; 3 | 4 | /** 5 | * Generate a linker method that links given hardcoded namepaths to URLs 6 | * 7 | * @param {Object} paths an object specified in documentation.yml of hard paths 8 | * @returns {Function} linker 9 | */ 10 | function pathsLinker(paths /* Object */) { 11 | return function (namespace) { 12 | if (paths[namespace]) { 13 | return paths[namespace]; 14 | } 15 | }; 16 | } 17 | 18 | /** 19 | * Given an array of functions, call each of them with `input` 20 | * and return the output of the first one that returns a truthy value. 21 | * 22 | * @param {Array} fns array of methods 23 | * @param {*} input any input 24 | * @returns {*} any output 25 | */ 26 | function firstPass(fns, input) { 27 | for (let i = 0; i < fns.length; i++) { 28 | const output = fns[i](input); 29 | if (output) { 30 | return output; 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * Create a linking method that takes a namepath and returns a URI 37 | * or a falsy value 38 | * 39 | * @param {Object} config - configuration value 40 | * @returns {Function} linker method 41 | */ 42 | export default class LinkerStack { 43 | constructor(config) { 44 | this.stack = []; 45 | 46 | if (config.defaultGlobals !== false) { 47 | this.stack.unshift(namespace => { 48 | if (namespace) { 49 | return globalsDocs.getDoc(namespace, config.defaultGlobalsEnvs); 50 | } 51 | }); 52 | } 53 | 54 | if (config.paths) { 55 | this.stack.unshift(pathsLinker(config.paths)); 56 | } 57 | 58 | this.link = this._link.bind(this); 59 | } 60 | 61 | /** 62 | * Given that the linker stack is a stack of functions, each of which might 63 | * be able to resolve the URL target of a given namespace, namespaceResolver 64 | * adds a function to the stack. You give it a list of comments and it 65 | * adds a function that, if it matches a namespace to a comment, runs 66 | * `resolver` on that comment's namespace in order to get a URL. This makes 67 | * it possible for themes to put each function on a separate page, or at 68 | * different anchors on the same page, and the resolver does stuff like 69 | * adding '#' in front of the namespace or turning the namespace into a URL 70 | * path. 71 | * 72 | * @param {Array} comments a list of comments 73 | * @param {Function} resolver a method that turns a namespace into a URL 74 | * @returns {LinkerStack} returns this 75 | * @private 76 | * @example 77 | * var linkerStack = new LinkerStack(options) 78 | * .namespaceResolver(comments, function (namespace) { 79 | * var slugger = new GithubSlugger(); 80 | * return '#' + slugger.slug(namespace); 81 | * }); 82 | */ 83 | namespaceResolver(comments, resolver) { 84 | const namespaces = {}; 85 | walk(comments, comment => { 86 | namespaces[comment.namespace] = true; 87 | }); 88 | this.stack.unshift(namespace => { 89 | if (namespaces[namespace] === true) { 90 | return resolver(namespace); 91 | } 92 | }); 93 | return this; 94 | } 95 | 96 | /** 97 | * Now that you've configured the LinkerStack with `namespaceResolver` 98 | * and a configuration, run it against a namepath. Might return a URL if 99 | * it can resolve a target, otherwise returns undefined. 100 | * 101 | * @param {string} namepath the namepath of a comment 102 | * @returns {string?} URL target or maybe undefined 103 | * @private 104 | */ 105 | _link(namepath) { 106 | return firstPass(this.stack, namepath); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/output/util/reroute_links.js: -------------------------------------------------------------------------------- 1 | import { visit } from 'unist-util-visit'; 2 | 3 | /** 4 | * Reroute inline jsdoc links in documentation 5 | * @param getHref a method that resolves namespaces 6 | * @param ast remark AST 7 | * @returns {Object} that ast with rerouted links 8 | * @private 9 | */ 10 | export default function rerouteLinks(getHref, ast) { 11 | visit(ast, 'link', function (node) { 12 | if ( 13 | node.jsdoc && 14 | !node.url.match(/^(http|https|\.)/) && 15 | getHref(node.url) 16 | ) { 17 | node.url = getHref(node.url); 18 | } 19 | }); 20 | return ast; 21 | } 22 | -------------------------------------------------------------------------------- /src/parsers/README.md: -------------------------------------------------------------------------------- 1 | ## Parser Streams 2 | 3 | file contents -> extracted comments 4 | 5 | Parser streams receive the content returned by input streams and extract 6 | JSDoc comments. The `javascript` stream goes further and also extracts 7 | the [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) 8 | of JavaScript source code in order to help `documentation` infer things 9 | about the source code's structure, like the naming of variables. 10 | -------------------------------------------------------------------------------- /src/parsers/javascript.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import t from '@babel/types'; 3 | import parse from '../parse.js'; 4 | import walkComments from '../extractors/comments.js'; 5 | import walkExported from '../extractors/exported.js'; 6 | import util from 'util'; 7 | import findTarget from '../infer/finders.js'; 8 | import { parseToAst } from './parse_to_ast.js'; 9 | 10 | const debuglog = util.debuglog('documentation'); 11 | 12 | /** 13 | * Receives a module-dep item, 14 | * reads the file, parses the JavaScript, and parses the JSDoc. 15 | * 16 | * @param {Object} data a chunk of data provided by module-deps 17 | * @param {Object} config config 18 | * @returns {Array} an array of parsed comments 19 | */ 20 | export default function parseJavaScript(data, config) { 21 | const visited = new Set(); 22 | const commentsByNode = new Map(); 23 | 24 | const ast = parseToAst(data.source, data.file); 25 | const addComment = _addComment.bind(null, visited, commentsByNode); 26 | 27 | const extensions = [] 28 | .concat(config.parseExtension, config.requireExtension) 29 | .filter(Boolean); 30 | 31 | return _.flatMap( 32 | config.documentExported 33 | ? [walkExported.bind(null, { extensions })] 34 | : [ 35 | walkComments.bind(null, 'leadingComments', true), 36 | walkComments.bind(null, 'innerComments', false), 37 | walkComments.bind(null, 'trailingComments', false) 38 | ], 39 | fn => fn(ast, data, addComment) 40 | ).filter(comment => comment && !comment.lends); 41 | } 42 | 43 | function _addComment( 44 | visited, 45 | commentsByNode, 46 | data, 47 | commentValue, 48 | commentLoc, 49 | path, 50 | nodeLoc, 51 | includeContext 52 | ) { 53 | // Avoid visiting the same comment twice as a leading 54 | // and trailing node 55 | const key = 56 | data.file + ':' + commentLoc.start.line + ':' + commentLoc.start.column; 57 | if (!visited.has(key)) { 58 | visited.add(key); 59 | 60 | const context /* : { 61 | loc: Object, 62 | file: string, 63 | sortKey: string, 64 | ast?: Object, 65 | code?: string 66 | }*/ = { 67 | loc: nodeLoc, 68 | file: data.file, 69 | sortKey: 70 | data.sortKey + ' ' + nodeLoc.start.line.toString().padStart(8, '0') 71 | }; 72 | 73 | if (includeContext) { 74 | // This is non-enumerable so that it doesn't get stringified in 75 | // output; e.g. by the documentation binary. 76 | Object.defineProperty(context, 'ast', { 77 | configurable: true, 78 | enumerable: false, 79 | value: path 80 | }); 81 | 82 | if (path.parentPath && path.parentPath.node) { 83 | const parentNode = path.parentPath.node; 84 | context.code = data.source.substring(parentNode.start, parentNode.end); 85 | } 86 | } 87 | const comment = parse(commentValue, commentLoc, context); 88 | if (includeContext) { 89 | commentsByNode.set((findTarget(path) || path).node, comment); 90 | 91 | if (t.isClassMethod(path) && path.node.kind === 'constructor') { 92 | // #689 93 | if ( 94 | comment.tags.some( 95 | tag => tag.title !== 'param' && tag.title !== 'hideconstructor' 96 | ) 97 | ) { 98 | debuglog( 99 | 'A constructor was documented explicitly: document along with the class instead' 100 | ); 101 | } 102 | 103 | const parentComment = commentsByNode.get( 104 | path.parentPath.parentPath.node 105 | ); 106 | if (parentComment) { 107 | parentComment.constructorComment = comment; 108 | return; 109 | } 110 | if (comment.hideconstructor) { 111 | return; 112 | } 113 | } 114 | } 115 | return comment; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/parsers/parse_to_ast.js: -------------------------------------------------------------------------------- 1 | import babelParser from '@babel/parser'; 2 | import path from 'path'; 3 | 4 | const TYPESCRIPT_EXTS = { 5 | '.ts': ['typescript'], 6 | '.tsx': ['typescript', 'jsx'] 7 | }; 8 | 9 | // this list is roughly the same as the one in prettier 10 | // https://github.com/prettier/prettier/blob/24d39a906834cf449304dc684b280a5ca9a0a6d7/src/language-js/parser-babel.js#L23 11 | export const standardBabelParserPlugins = [ 12 | 'classPrivateMethods', 13 | 'classPrivateProperties', 14 | 'classProperties', 15 | 'classStaticBlock', 16 | 'decimal', 17 | ['decorators', { decoratorsBeforeExport: false }], 18 | 'doExpressions', 19 | 'exportDefaultFrom', 20 | 'functionBind', 21 | 'functionSent', 22 | 'importAssertions', 23 | 'moduleBlocks', 24 | 'moduleStringNames', 25 | 'partialApplication', 26 | ['pipelineOperator', { proposal: 'minimal' }], 27 | 'privateIn', 28 | ['recordAndTuple', { syntaxType: 'hash' }], 29 | 'throwExpressions', 30 | 'v8intrinsic' 31 | ]; 32 | 33 | function getParserOpts(file) { 34 | return { 35 | allowImportExportEverywhere: true, 36 | sourceType: 'module', 37 | plugins: [ 38 | ...standardBabelParserPlugins, 39 | ...(TYPESCRIPT_EXTS[path.extname(file || '')] || ['flow', 'jsx']) 40 | ] 41 | }; 42 | } 43 | 44 | /** 45 | * Convert flow comment types into flow annotations so that 46 | * they end up in the final AST. If the source does not contain 47 | * a flow pragma, the code is returned verbatim. 48 | * @param {*} source code with flow type comments 49 | * @returns {string} code with flow annotations 50 | */ 51 | export function commentToFlow(source) { 52 | if (!/@flow/.test(source)) return source; 53 | return source 54 | .replace(/\/\*::([^]+?)\*\//g, '$1') 55 | .replace(/\/\*:\s*([^]+?)\s*\*\//g, ':$1'); 56 | } 57 | 58 | export function parseToAst(source, file) { 59 | return babelParser.parse(commentToFlow(source), getParserOpts(file)); 60 | } 61 | -------------------------------------------------------------------------------- /src/remark-jsDoc-link.js: -------------------------------------------------------------------------------- 1 | import { findAndReplace } from 'mdast-util-find-and-replace'; 2 | import { markdownLineEnding } from 'micromark-util-character'; 3 | 4 | const link = '@link'; 5 | const tutorial = '@tutorial'; 6 | 7 | function tokenizeJsDoclink(effects, ok, nok) { 8 | let index = 0; 9 | let focus = link; 10 | 11 | function atext(code) { 12 | if (index !== link.length) { 13 | if (focus.charCodeAt(index) === code) { 14 | effects.consume(code); 15 | index++; 16 | return atext; 17 | } else if (tutorial.charCodeAt(index) === code) { 18 | focus = tutorial; 19 | } 20 | return nok(code); 21 | } 22 | if (code === 125) { 23 | effects.consume(code); 24 | effects.exit('literalJsDoclink'); 25 | return ok(code); 26 | } 27 | 28 | if (markdownLineEnding(code)) { 29 | return nok(code); 30 | } 31 | 32 | effects.consume(code); 33 | return atext; 34 | } 35 | 36 | return function (code) { 37 | effects.enter('literalJsDoclink'); 38 | effects.consume(code); 39 | return atext; 40 | }; 41 | } 42 | 43 | const text = {}; 44 | text[123] = { 45 | tokenize: tokenizeJsDoclink, 46 | previous(code) { 47 | return code === null || code === 32 || markdownLineEnding(code); 48 | } 49 | }; 50 | 51 | const linkRegExp = /\{@link\s+(.+?)(?:[\s|](.*?))?\}/; 52 | const tutorialRegExp = /\{@tutorial\s+(.+?)(?:[\s|](.*?))?\}/; 53 | 54 | /** 55 | * A remark plugin that installs 56 | * for JSDoc inline `{@link}` and `{@tutorial}` tags. 57 | * 58 | * This does not handle the `[text]({@link url})` and `[text]({@tutorial url})` forms of these tags. 59 | * That's a JSDoc misfeature; just use regular markdown syntax instead: `[text](url)`. 60 | * 61 | * @returns {Function} 62 | */ 63 | export default function () { 64 | const data = this.data(); 65 | function replace(type) { 66 | return (match, matchUrl, matchValue) => { 67 | return { 68 | type, 69 | url: matchUrl, 70 | title: null, 71 | jsdoc: true, 72 | children: [ 73 | { 74 | type: 'text', 75 | value: matchValue || matchUrl 76 | } 77 | ] 78 | }; 79 | }; 80 | } 81 | 82 | add('micromarkExtensions', { text }); 83 | add('fromMarkdownExtensions', { 84 | transforms: [ 85 | function (markdownAST) { 86 | return findAndReplace(markdownAST, [ 87 | [new RegExp(linkRegExp.source, 'g'), replace('link')], 88 | [new RegExp(tutorialRegExp.source, 'g'), replace('tutorial')] 89 | ]); 90 | } 91 | ], 92 | enter: { 93 | literalJsDoclink(token) { 94 | const str = this.sliceSerialize(token); 95 | let match = null; 96 | if (str.startsWith('{@link')) { 97 | match = linkRegExp.exec(str); 98 | } else { 99 | match = tutorialRegExp.exec(str); 100 | } 101 | 102 | this.enter(replace('link')(...match), token); 103 | } 104 | }, 105 | exit: { 106 | literalJsDoclink(token) { 107 | this.exit(token); 108 | } 109 | } 110 | }); 111 | function add(field, value) { 112 | if (data[field]) data[field].push(value); 113 | else data[field] = [value]; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/remark-parse.js: -------------------------------------------------------------------------------- 1 | import { remark } from 'remark'; 2 | import gfm from 'remark-gfm'; 3 | import removePosition from './remark-remove-position.js'; 4 | import jsDocLink from './remark-jsDoc-link.js'; 5 | 6 | /** 7 | * Parse a string of Markdown into a Remark 8 | * abstract syntax tree. 9 | * 10 | * @param {string} string markdown text 11 | * @returns {Object} abstract syntax tree 12 | * @private 13 | */ 14 | export default remark().use([jsDocLink, gfm, removePosition]).parse; 15 | -------------------------------------------------------------------------------- /src/remark-remove-position.js: -------------------------------------------------------------------------------- 1 | import { visit } from 'unist-util-visit'; 2 | 3 | export default function () { 4 | const data = this.data(); 5 | add('fromMarkdownExtensions', { 6 | transforms: [ 7 | function (markdownAST) { 8 | visit(markdownAST, node => delete node.position); 9 | } 10 | ] 11 | }); 12 | function add(field, value) { 13 | if (data[field]) data[field].push(value); 14 | else data[field] = [value]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/type_annotation.js: -------------------------------------------------------------------------------- 1 | import t from '@babel/types'; 2 | import flowDoctrine from './flow_doctrine.js'; 3 | import tsDoctrine from './ts_doctrine.js'; 4 | 5 | function typeAnnotation(type) { 6 | if (t.isFlow(type)) { 7 | if (t.isTypeAnnotation(type)) { 8 | type = type.typeAnnotation; 9 | } 10 | 11 | return flowDoctrine(type); 12 | } 13 | 14 | if (t.isTSTypeAnnotation(type)) { 15 | type = type.typeAnnotation; 16 | } 17 | 18 | return tsDoctrine(type); 19 | } 20 | 21 | export default typeAnnotation; 22 | -------------------------------------------------------------------------------- /src/walk.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Apply a function to all comments within a hierarchy: this iterates 3 | * through children in the 'members' property. 4 | * 5 | * @param {Array} comments an array of nested comments 6 | * @param {Function} fn a walker function 7 | * @param {Object} [options] options passed through to walker function 8 | * @returns {Array} comments 9 | */ 10 | export default function walk(comments, fn, options) { 11 | comments.forEach(comment => { 12 | fn(comment, options); 13 | for (const scope in comment.members) { 14 | walk(comment.members[scope], fn, options); 15 | } 16 | }); 17 | return comments; 18 | } 19 | --------------------------------------------------------------------------------