Showing preview only (384K chars total). Download the full file or copy to clipboard to get everything.
Repository: mozilla/sphinx-js
Branch: master
Commit: ec2c7bd0c99d
Files: 159
Total size: 345.3 KB
Directory structure:
gitextract_jnq5w43z/
├── .editorconfig
├── .github/
│ ├── codecov.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── check_ts.yml
│ ├── ci.yml
│ ├── release.yml
│ └── test_report.yml
├── .gitignore
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTORS
├── LICENSE
├── README.md
├── biome.json
├── noxfile.py
├── pyproject.toml
├── sphinx_js/
│ ├── __init__.py
│ ├── analyzer_utils.py
│ ├── directives.py
│ ├── ir.py
│ ├── js/
│ │ ├── cli.ts
│ │ ├── convertTopLevel.ts
│ │ ├── convertType.ts
│ │ ├── importHooks.mjs
│ │ ├── ir.ts
│ │ ├── main.ts
│ │ ├── package.json
│ │ ├── redirectPrivateAliases.ts
│ │ ├── registerImportHook.mjs
│ │ ├── sphinxJsConfig.ts
│ │ ├── tsconfig.json
│ │ ├── typedocPatches.ts
│ │ └── typedocPlugin.ts
│ ├── jsdoc.py
│ ├── parsers.py
│ ├── py.typed
│ ├── renderers.py
│ ├── suffix_tree.py
│ ├── templates/
│ │ ├── attribute.rst
│ │ ├── class.rst
│ │ ├── common.rst
│ │ └── function.rst
│ └── typedoc.py
└── tests/
├── __init__.py
├── conftest.py
├── roots/
│ ├── test-incremental_js/
│ │ ├── a.js
│ │ ├── a.rst
│ │ ├── a_b.rst
│ │ ├── b.rst
│ │ ├── conf.py
│ │ ├── index.rst
│ │ ├── inner/
│ │ │ └── b.js
│ │ ├── jsdoc.json
│ │ └── unrelated.rst
│ └── test-incremental_ts/
│ ├── a.rst
│ ├── a.ts
│ ├── a_b.rst
│ ├── b.rst
│ ├── conf.py
│ ├── index.rst
│ ├── inner/
│ │ └── b.ts
│ ├── tsconfig.json
│ └── unrelated.rst
├── sphinxJsConfig.ts
├── test.ts
├── test_build_js/
│ ├── source/
│ │ ├── code.js
│ │ ├── docs/
│ │ │ ├── autoattribute.rst
│ │ │ ├── autoattribute_deprecated.rst
│ │ │ ├── autoattribute_example.rst
│ │ │ ├── autoattribute_see.rst
│ │ │ ├── autoclass.rst
│ │ │ ├── autoclass_alphabetical.rst
│ │ │ ├── autoclass_deprecated.rst
│ │ │ ├── autoclass_example.rst
│ │ │ ├── autoclass_exclude_members.rst
│ │ │ ├── autoclass_members.rst
│ │ │ ├── autoclass_members_list.rst
│ │ │ ├── autoclass_members_list_star.rst
│ │ │ ├── autoclass_no_paramnames.rst
│ │ │ ├── autoclass_private_members.rst
│ │ │ ├── autoclass_see.rst
│ │ │ ├── autofunction_callback.rst
│ │ │ ├── autofunction_defaults_code.rst
│ │ │ ├── autofunction_defaults_doclet.rst
│ │ │ ├── autofunction_deprecated.rst
│ │ │ ├── autofunction_destructured_params.rst
│ │ │ ├── autofunction_example.rst
│ │ │ ├── autofunction_explicit.rst
│ │ │ ├── autofunction_long.rst
│ │ │ ├── autofunction_minimal.rst
│ │ │ ├── autofunction_see.rst
│ │ │ ├── autofunction_short.rst
│ │ │ ├── autofunction_static.rst
│ │ │ ├── autofunction_typedef.rst
│ │ │ ├── autofunction_variadic.rst
│ │ │ ├── avoid_shadowing.rst
│ │ │ ├── conf.py
│ │ │ ├── getter_setter.rst
│ │ │ ├── index.rst
│ │ │ ├── injection.rst
│ │ │ ├── union.rst
│ │ │ └── unwrapped.rst
│ │ └── more_code.js
│ └── test_build_js.py
├── test_build_ts/
│ ├── source/
│ │ ├── class.ts
│ │ ├── docs/
│ │ │ ├── async_function.rst
│ │ │ ├── autoclass_class_with_interface_and_supers.rst
│ │ │ ├── autoclass_constructorless.rst
│ │ │ ├── autoclass_exported.rst
│ │ │ ├── autoclass_interface_optionals.rst
│ │ │ ├── autoclass_star.rst
│ │ │ ├── automodule.rst
│ │ │ ├── autosummary.rst
│ │ │ ├── conf.py
│ │ │ ├── deprecated.rst
│ │ │ ├── example.rst
│ │ │ ├── getset.rst
│ │ │ ├── index.rst
│ │ │ ├── inherited_docs.rst
│ │ │ ├── predicate.rst
│ │ │ ├── sphinx_link_in_description.rst
│ │ │ ├── symbol.rst
│ │ │ └── xrefs.rst
│ │ ├── empty.ts
│ │ ├── module.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ └── test_build_ts.py
├── test_build_xref_none/
│ ├── source/
│ │ ├── docs/
│ │ │ ├── conf.py
│ │ │ └── index.rst
│ │ └── main.ts
│ └── test_build_xref_none.py
├── test_common_mark/
│ ├── source/
│ │ ├── code.js
│ │ └── docs/
│ │ ├── conf.py
│ │ └── index.md
│ └── test_common_mark.py
├── test_dot_dot_paths/
│ ├── source/
│ │ ├── code.js
│ │ └── docs/
│ │ ├── conf.py
│ │ └── index.rst
│ └── test_dot_dot_paths.py
├── test_incremental.py
├── test_init.py
├── test_ir.py
├── test_jsdoc_analysis/
│ ├── source/
│ │ ├── class.js
│ │ └── function.js
│ └── test_jsdoc.py
├── test_parsers.py
├── test_paths.py
├── test_renderers.py
├── test_suffix_tree.py
├── test_testing.py
├── test_typedoc_analysis/
│ ├── source/
│ │ ├── exports.ts
│ │ ├── nodes.ts
│ │ ├── subdir/
│ │ │ └── pathSegments.ts
│ │ ├── tsconfig.json
│ │ └── types.ts
│ └── test_typedoc_analysis.py
└── testing.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8
end_of_line = lf
[*.py]
indent_size = 4
[*.rst]
indent_size = 4
================================================
FILE: .github/codecov.yml
================================================
comment: false
codecov:
branch: main
require_ci_to_pass: false
notify:
wait_for_ci: false
================================================
FILE: .github/dependabot.yml
================================================
# Keep GitHub Actions up to date with GitHub's Dependabot...
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
version: 2
updates:
- package-ecosystem: github-actions
directory: /
groups:
github-actions:
patterns:
- "*" # Group all Actions updates into a single larger pull request
schedule:
interval: weekly
================================================
FILE: .github/workflows/check_ts.yml
================================================
name: check-ts
on:
pull_request:
permissions: write-all
jobs:
ts:
if: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Get diff lines
id: diff
uses: Equip-Collaboration/diff-line-numbers@v1.1.0
with:
include: '["\\.ts$"]'
- name: Detecting files changed
id: files
uses: umani/changed-files@v4.2.0
with:
repo-token: ${{ github.token }}
pattern: '^.*\.ts$'
- name: List files changed (you can remove this step, for monitoring only)
run: |
echo 'Files modified: ${{steps.files.outputs.files_updated}}'
echo 'Files added: ${{steps.files.outputs.files_created}}'
echo 'Files removed: ${{steps.files.outputs.files_deleted}}'
- uses: Arhia/action-check-typescript@v1.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
use-check: true
check-fail-mode: added
files-changed: ${{steps.files.outputs.files_updated}}
files-added: ${{steps.files.outputs.files_created}}
files-deleted: ${{steps.files.outputs.files_deleted}}
line-numbers: ${{steps.diff.outputs.lineNumbers}}
output-behaviour: both
comment-behaviour: new
ts-config-path: "./sphinx_js/js/tsconfig.json"
================================================
FILE: .github/workflows/ci.yml
================================================
---
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
test:
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
name: Python ${{ matrix.python-version}}
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Update pip and install dev requirements
run: |
python -m pip install --upgrade pip
pip install nox
- name: Test
run: nox -s tests-${{ matrix.python-version }}
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- uses: actions/upload-artifact@v5
if: success() || failure()
with:
name: test-results-${{ matrix.python-version }}
path: test-results.xml
test-typedoc-versions:
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
python-version: ["3.12"]
typedoc-version: ["0.25", "0.26", "0.27", "0.28"]
name: Python ${{ matrix.python-version}} + typedoc ${{ matrix.typedoc-version }}
steps:
- uses: actions/checkout@v6
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 22
- name: Set up Python
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Update pip and install dev requirements
run: |
python -m pip install --upgrade pip
pip install nox
- name: Test
run: nox -s "test_typedoc-${{ matrix.python-version }}(typedoc='${{ matrix.typedoc-version }}')"
- uses: actions/upload-artifact@v5
if: success() || failure()
with:
name: test_typedoc-results-${{ matrix.python-version }}-${{ matrix.typedoc-version }}
path: test-results.xml
test-sphinx-versions:
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
python-version: ["3.12"]
sphinx-version: ["6"]
name: Test sphinx 6
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Update pip and install dev requirements
run: |
python -m pip install --upgrade pip
pip install nox
- name: Test
run: nox -s "test_sphinx_6-${{ matrix.python-version }}"
- uses: actions/upload-artifact@v5
if: success() || failure()
with:
name: test_sphinx_6-${{ matrix.python-version }}
path: test-results.xml
================================================
FILE: .github/workflows/release.yml
================================================
name: CD
on:
release:
types: [published]
workflow_dispatch:
schedule:
- cron: "0 3 * * 1"
env:
FORCE_COLOR: 3
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v4.2.2
with:
# include tags so that hatch-vcs can infer the version
fetch-depth: 0
# switch to fetch-tags: true when the following is fixed
# see https://github.com/actions/checkout/issues/2041
# fetch-tags: true
- name: Setup Python
uses: actions/setup-python@bba65e51ff35d50c6dbaaacd8a4681db13aa7cb4 # v5.6.0
with:
python-version: "3.12"
- name: Build
run: |
python -m pip install build
python -m build .
- name: Store the distribution packages
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: python-package-distributions
path: dist/
if-no-files-found: error
publish:
name: Publish to PyPI
needs: [build]
runs-on: ubuntu-latest
if: github.event_name == 'release' && github.event.action == 'published'
environment:
name: pypi
url: https://pypi.org/p/sphinx-js
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
attestations: write
contents: read
steps:
- name: Download all the dists
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
path: dist/
merge-multiple: true
- name: Generate artifact attestations
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with:
subject-path: "dist/*"
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
================================================
FILE: .github/workflows/test_report.yml
================================================
name: "Test Report"
on:
workflow_run:
workflows: ["CI"]
types:
- completed
jobs:
report:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
runs-on: ubuntu-latest
steps:
- uses: dorny/test-reporter@v2.2.0
with:
artifact: test-results-${{ matrix.python-version }}
name: Test report - ${{ matrix.python-version }}
path: test-results.xml
reporter: java-junit
report-typedoc:
strategy:
fail-fast: false
matrix:
python-version: ["3.12"]
typedoc-version: ["0.25"]
runs-on: ubuntu-latest
steps:
- uses: dorny/test-reporter@v2.2.0
with:
artifact: test_typedoc-results-${{ matrix.python-version }}-${{ matrix.typedoc-version }}
name: Test report - Python ${{ matrix.python-version}} + typedoc ${{ matrix.typedoc-version }}
path: test-results.xml
reporter: java-junit
report-sphinx:
strategy:
fail-fast: false
matrix:
python-version: ["3.12"]
sphinx-version: ["6"]
runs-on: ubuntu-latest
steps:
- uses: dorny/test-reporter@v2.2.0
with:
artifact: test_sphinx_6-${{ matrix.python-version }}
name: Test report - Sphinx 6
path: test-results.xml
reporter: java-junit
================================================
FILE: .gitignore
================================================
/.eggs
/.tox
/.pytest_cache
/build
/dist
/node_modules
_build
sphinx_js.egg-info/
# Python 3
*/__pycache__/*
# Python 2.7
*.pyc
# Pycharm config
.idea
# VsCode config
.vscode
.DS_Store
venv
.venv
coverage.xml
test-results.xml
node_modules
tsconfig.tsbuildinfo
================================================
FILE: .pre-commit-config.yaml
================================================
default_language_version:
python: "3.10"
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v4.4.0"
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-merge-conflict
exclude: README.MD
- id: check-symlinks
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.9.1"
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/biomejs/pre-commit
rev: "v0.6.1"
hooks:
- id: biome-format
additional_dependencies: ["@biomejs/biome@2.1.1"]
types_or: [javascript, ts]
- repo: https://github.com/rstcheck/rstcheck
rev: "v6.2.5"
hooks:
- id: rstcheck
exclude: (tests/|sphinx_js/templates)
additional_dependencies: ["rstcheck[sphinx,toml]"]
- repo: https://github.com/codespell-project/codespell
rev: "v2.2.5"
hooks:
- id: codespell
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.5.1"
hooks:
- id: mypy
exclude: (tests/)
args: []
additional_dependencies:
- attrs
- cattrs
- jinja2
- nox
- pydantic
- pytest
- sphinx
- types-docutils
- types-parsimonious
- types-setuptools
ci:
autoupdate_schedule: "quarterly"
================================================
FILE: CHANGELOG.md
================================================
## Changelog
### 5.0.3: (March 30th, 2026)
- Test Python 3.14 in CI (#302)
- Fix compatibility with sphinx 9 (#301)
- Fix: Errors generated from invalid xrefs should fail build (#300)
### 5.0.2: (October 17th, 2025)
- Unpin markupsafe by @fmhoeger (#287)
### 5.0.1: (September 17th, 2025)
- Fixed a bug that comment of the arrow function in the interface is not rendered correctly.
(pyodide/sphinx-js#284)
### 5.0.0: (July 2nd, 2025)
- Dropped support for Python 3.9 (pyodide/sphinx-js-fork#7)
- Dropped support for typedoc 0.15, added support for typedoc 0.25--0.28 (
pyodide/sphinx-js-fork#11, pyodide/sphinx-js-fork#22,
pyodide/sphinx-js-fork#31, pyodide/sphinx-js-fork#39,
pyodide/sphinx-js-fork#41, pyodide/sphinx-js-fork#43
pyodide/sphinx-js-fork#52, pyodide/sphinx-js-fork#53,
pyodide/sphinx-js-fork#54, pyodide/sphinx-js-fork#174,
#266)
- Added handling for TypeScript type parameters and type bounds.
(pyodide/sphinx-js-fork#25)
- Only monkeypatch Sphinx classes when sphinx_js extension is used
(pyodide/sphinx-js-fork#27)
- Allow using installation of `typedoc` or `jsdoc` from `node_modules`
instead of requiring global install. (pyodide/sphinx-js-fork#33)
- Handle markdown style codepens correctly in typedoc comments.
(pyodide/sphinx-js-fork#47)
- Added support for destructuring the documentation of keyword arguments in
TypeScript using the `@destructure` tag or the
`shouldDestructureArg` hook. (
pyodide/sphinx-js-fork#48, pyodide/sphinx-js-fork#74,
pyodide/sphinx-js-fork#75, pyodide/sphinx-js-fork#101,
pyodide/sphinx-js-fork#128)
- Added rendering for cross references in TypeScript types. (
pyodide/sphinx-js-fork#51, pyodide/sphinx-js-fork#56,
pyodide/sphinx-js-fork#67, pyodide/sphinx-js-fork#81,
pyodide/sphinx-js-fork#82, pyodide/sphinx-js-fork#83,
pyodide/sphinx-js-fork#153, pyodide/sphinx-js-fork#160)
- Added rendering for function types in TypeScript documentation. (
pyodide/sphinx-js-fork#55, pyodide/sphinx-js-fork#58,
pyodide/sphinx-js-fork#59)
- Add async prefix to async functions (pyodide/sphinx-js-fork#65).
- Added the `sphinx-js_type` css class around all types in documentation. This
allows applying custom css just to types (pyodide/sphinx-js-fork#85)
- Added `ts_type_bold` config option that applies css to `.sphinx-js_type`
to render all types as bold.
- Added `js:automodule` directive (pyodide/sphinx-js-fork#108)
- Added `js:autosummary` directive (pyodide/sphinx-js-fork#109)
- Added rendering for `queryType` (e.g., `let y: typeof x;`)
(pyodide/sphinx-js-fork#124)
- Added rendering for `typeOperator` (e.g., `let y: keyof x`)
(pyodide/sphinx-js-fork#125)
- Fixed crash when objects are reexported. (pyodide/sphinx-js-fork#126)
- Added `jsdoc_tsconfig_path` which can specify the path to the
`tsconfig.json` file that should be used. (pyodide/sphinx-js-fork#116)
- Added a `js:interface` directive (pyodide/sphinx-js-fork#138).
- Removed parentheses from xrefs to classes (pyodide/sphinx-js-fork#155).
- Added a `:js:typealias:` directive (pyodide/sphinx-js-fork#156).
- Added rendering for conditional, indexed access, inferred, mapped, optional,
rest, and template litreal types (pyodide/sphinx-js-fork#157).
- Added readonly prefix to readonly properties (pyodide/sphinx-js-fork#158).
### 4.0.0: (December 23rd, 2024)
- Drop support for Python 3.8.
- Add support for Python 3.12 and 3.13.
- Add support for Sphinx 8.x.x.
- Get CI working again.
- Drop pin for MarkupSafe. (#244)
- Add dependabot checking for GitHub actions. (Christian Clauss)
- Fix wheel contents to not include tests. (#241)
Thank you to Will Kahn-Greene and Christian Clauss!
### 3.2.2: (September 20th, 2023)
- Remove Sphinx upper-bound requirement. (#227)
- Drop support for Python 3.7. (#228)
Thank you to Will Kahn-Greene!
### 3.2.1: (December 16th, 2022)
- Fix xrefs to static functions. (#178)
- Add support for jsdoc 4.0.0. (#215)
Thank you to xsjad0 and Will Kahn-Greene!
### 3.2.0: (December 13th, 2022)
- Add "static" in front of static methods.
- Pin Jinja2 and markupsafe versions. (#190)
- Track dependencies; do not read all documents. This improves speed of
incremental updates. (#194)
- Support Python 3.10 and 3.11. (#186)
- Support Sphinx >= 4.1.0. (#209)
- Fix types warning for `js_source_path` configuration item. (#182)
Thank you Stefan 'hr' Berder, David Huggins-Daines, Nick Alexander,
mariusschenzle, Erik Rose, lonnen, and Will Kahn-Greene!
### 3.1.2: (April 15th, 2021)
- Remove our declared dependency on `docutils` to work around the way pip's
greedy dependency resolver reacts to the latest version of Sphinx. pip
fails when pip-installing sphinx-js because pip sees our "any version of
docutils" declaration first (which resolves greedily to the latest version,
0.17) but later encounters Sphinx's apparently new `<0.17` constraint and
gives up. We can revert this when pip's `--use-feature=2020-resolver`
becomes the default.
### 3.1.1: (March 23rd, 2021)
- Rewrite large parts of the suffix tree that powers path lookup. This fixes
several crashes.
### 3.1: (September 10th, 2020)
- Re-architect language analysis. There is now a well-documented intermediate
representation between JSDoc- and TypeDoc-emitted JSON and the renderers.
This should make it much faster to merge PRs.
- Rewrite much of the TypeScript analysis engine so it feeds into the new IR.
- TypeScript analysis used to crash if your codebase contained any
overloaded functions. This no longer happens; we now arbitrarily use only
the first function signature of each overloaded function.
- Add support for static properties on TS classes.
- Support variadic args in TS.
- Support intersection types (`foo & bar`) in TS.
- Remove the "exported from" module links from classes and interfaces.
Functions never had them. Let's see if we miss them.
- Pathnames for TypeScript objects no longer spuriously use `~` after the
filename path segment; now they use `.` as in JS.
- More generally, TS pathnames are now just like JS ones. There is no more
`external:` prefix in front of filenames or `module:` in front of
namespace names.
- TS analyzer no longer cares with the current working directory is.
- Tests now assert only what they care about rather than being brittle to
the point of prohibiting any change.
- No longer show args in the arg list that are utterly uninformative, lacking
both description and type info.
- Class attributes are now listed before methods unless manally ordered with
`:members:`.
### 3.0.1: (August 10th, 2020)
- Don't crash when encountering a `../` prefix on an object path. This can
happen behind the scenes when `root_for_relative_js_paths` is set inward
of the JS code.
### 3.0: (July 14th, 2020)
- Make compatible with Sphinx 3, which requires Python 3.
- Drop support for Python 2.
- Make sphinx-js not care what the current working directory is, except for
the TypeScript analyzer, which needs further work.
- Properly RST-escape return types.
### 2.8: (September 16th, 2019)
- Display generic TypeScript types properly. Make fields come before methods.
(Paul Grau)
- Combine constructor and class documentation at the top TypeScript classes.
(Sebastian Weigand)
- Switch to pytest as the testrunner. (Sebastian Weigand)
- Add optional caching of JSDoc output, for large codebases. (Patrick Browne)
- Fix the display of union types in TypeScript. (Sebastian Weigand)
- Fix parsing breakage that began in typedoc 0.14.0. (Paul Grau)
- Fix a data-intake crash with TypeScript. (Cristiano Santos)
### 2.7.1: (November 16th, 2018)
- Fix a crash that would happen sometimes with UTF-8 on Windows. #67.
- Always use conf.py's dir for JSDoc's working dir. #78. (Thomas Khyn)
### 2.7: (August 2nd, 2018))
- Add experimental TypeScript support. (Wim Yedema)
### 2.6: (July 26th, 2018)
- Add support for `@deprecated` and `@see`. (David Li)
- Notice and document JS variadic params nicely. (David Li)
- Add linter to codebase.
### 2.5: (April 20th, 2018)
- Use documented `@params` to help fill out the formal param list for a
function. This keeps us from missing params that use destructuring. (flozz)
- Improve error reporting when JSDoc is missing.
- Add extracted default values to generated formal param lists. (flozz and
erikrose)
### 2.4: (March 21, 2018)
- Support the `@example` tag. (lidavidm)
- Work under Windows. Before, we could hardly find any documentation. (flozz)
- Properly unwrap multiple-line JSDoc tags, even if they have Windows line
endings. (Wim Yedema)
- Drop support for Python 3.3, since Sphinx has also done so.
- Fix build-time crash when using recommonmark (for Markdown support) under
Sphinx >=1.7.1. (jamrizzi)
### 2.3.1: (January 11th, 2018)
- Find the `jsdoc` command on Windows, where it has a different name. Then
patch up process communication so it doesn't hang.
### 2.3: (November 1st, 2017)
- Add the ability to say "\*" within the `autoclass :members:` option,
meaning "and all the members that I didn't explicitly list".
### 2.2: (October 10th, 2017)
- Add `autofunction` support for `@callback` tags. (krassowski)
- Add experimental `autofunction` support for `@typedef` tags. (krassowski)
- Add a nice error message for when JSDoc can't find any JS files.
- Pin six more tightly so `python_2_unicode_compatible` is sure to be around.
### 2.1: (August 30th, 2017)
- Allow multiple folders in `js_source_path`. This is useful for gradually
migrating large projects, one folder at a time, to JSDoc. Introduce
`root_for_relative_js_paths` to keep relative paths unambiguous in the
face of multiple source paths.
- Aggregate PathTaken errors, and report them all at once. This means you
don't have to run JSDoc repeatedly while cleaning up large projects.
- Fix a bytes-vs-strings issue that crashed on versions of Python 3 before
3.6. (jhkennedy)
- Tolerate JS files that have filename extensions other than ".js". Before,
when combined with custom JSDoc configuration that ingested such files,
incorrect object pathnames were generated, which led to spurious "No JSDoc
documentation was found for object ..." errors.
### 2.0.1: (July 13th, 2017)
- Fix spurious syntax errors while loading large JSDoc output by writing it
to a temp file first. (jhkennedy)
### 2.0: (May 4th, 2017)
- Deal with ambiguous object paths. Symbols with identical JSDoc longnames
(such as two top-level things called "foo" in different files) will no
longer have one shadow the other. Introduce an unambiguous path convention
for referring to objects. Add a real parser to parse them rather than the
dirty tricks we were using before. Backward compatibility breaks a little,
because ambiguous references are now a fatal error, rather than quietly
referring to the last definition JSDoc happened to encounter.
- Index everything into a suffix tree so you can use any unique path suffix
to refer to an object.
- Other fallout of having a real parser:
- Stop supporting "-" as a namepath separator.
- No longer spuriously translate escaped separators in namepaths into dots.
- Otherwise treat paths and escapes properly. For example, we can now
handle symbols that contain "(".
- Fix KeyError when trying to gather the constructor params of a plain old
object labeled as a `@class`.
### 1.5.2: (March 22th, 2017)
- Fix crash while warning that a specified longname isn't found.
### 1.5.1: (March 20th, 2017)
- Sort `:members:` alphabetically when an order is not explicitly specified.
### 1.5: (March 17th, 2017)
- Add `:members:` option to `autoclass`.
- Add `:private-members:` and `:exclude-members:` options to go with it.
- Significantly refactor to allow directive classes to talk to each other.
### 1.4: (March 10th, 2017)
- Add `jsdoc_config_path` option.
### 1.3.1: (March 6th, 2017)
- Tolerate @args and other info field lines that are wrapped in the source
code.
- Cite the file and line of the source comment in Sphinx-emitted warnings and
errors.
### 1.3: (February 21st, 2017)
- Add `autoattribute` directive.
### 1.2: (February 14th, 2017)
- Always do full rebuilds; don't leave pages stale when JS code has changed
but the RSTs have not.
- Make Python-3-compatible.
- Add basic `autoclass` directive.
### 1.1: (February 13th, 2017)
- Add `:short-name:` option.
### 1.0: (February 7th, 2017)
- Initial release, with just `js:autofunction`
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Code of Conduct
## Conduct
We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic.
- Please be kind and courteous. There’s no need to be mean or rude.
- Please avoid using usernames that are overtly sexual or otherwise might detract from a friendly, safe, and welcoming environment for all.
- Respect that people have differences of opinion and that every design or implementation choice carries trade-offs. There is seldom a single right answer.
- We borrow the Recurse Center’s [“social rules”](https://www.recurse.com/manual#sub-sec-social-rules): no feigning surprise, no well-actually’s, no backseat driving, and no subtle -isms.
- Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. All feedback should be constructive in nature. If you need more detailed guidance around giving feedback, consult [Digital Ocean’s Code of Conduct](https://github.com/digitalocean/engineering-code-of-conduct#giving-and-receiving-feedback)
- It is unacceptable to insult, demean, or harass anyone. We interpret the term “harassment” as defined in the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md#4-unacceptable-behavior); if you are not sure about what harassment entails, please read their definition. In particular, we don’t tolerate behavior that excludes people in socially marginalized groups.
- Private harassment is also unacceptable. No matter who you are, please contact any of the Pyodide core team members immediately if you are being harassed or made uncomfortable by a community member. Whether you are a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back.
- Likewise spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
## Moderation
These are the policies for upholding our community’s standards of conduct. If you feel that a thread needs moderation, please contact the Pyodide core team.
1. Remarks that violate the Pyodide standards of conduct are not allowed. This includes hateful, hurtful, oppressive, or exclusionary remarks. (Cursing is allowed, but never targeting another community member, and never in a hateful manner.)
2. Remarks that moderators find inappropriate are not allowed, even if they do not break a rule explicitly listed in the code of conduct.
3. Moderators will first respond to such remarks with a warning.
4. If the warning is unheeded, the offending community member will be temporarily banned.
5. If the community member comes back and continues to make trouble, they will be permanently banned.
6. Moderators may choose at their discretion to un-ban the community member if they offer the offended party a genuine apology.
7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, in private. Complaints about bans in-channel are not allowed.
8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others.
9. In the Pyodide community we strive to go the extra mile to look out for each other. Don’t just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they’re off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely.
10. If someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could have communicated better — remember that it’s your responsibility to make your fellow Pyodide community members comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about science and cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.
11. The enforcement policies listed above apply to all official Pyodide venues. If you wish to use this code of conduct for your own project, consider making a copy with your own moderation policy so as to avoid confusion.
Adapted from the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html), with further reference from [Digital Ocean Code of Conduct](https://github.com/digitalocean/engineering-code-of-conduct#giving-and-receiving-feedback), the [Recurse Center](https://www.recurse.com/code-of-conduct), the [Citizen Code of Conduct](http://citizencodeofconduct.org/), and the [Contributor Covenant](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html).
================================================
FILE: CONTRIBUTORS
================================================
sphinx-js was originally written and maintained by Erik Rose and various
contributors within and without the Mozilla Corporation and Foundation.
It is now part of the Pyodide organization.
It is currently maintained by Hood Chatham.
Maintainer emeritus:
* Will Kahn-Greene
* Erik Rose
* Lonnen
Contributors:
* Cristiano Santos
* David Huggins-Daines
* David Li
* Fabien LOISON
* Igor Loskutov
* Jam Risser
* Jared Dillard
* Joseph H Kennedy
* krassowski
* mariusschenzle
* Mike Cooper
* Nicholas Bollweg
* Nick Alexander
* Patrick Browne
* Paul Grau
* Robert Helmer
* Sebastian Weigand
* Staś Małolepszy
* Stefan 'hr' Berder
* s-weigand
* Tavian Barnes
* Thomas Khyn
* Wim Yedema
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 Mozilla Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# sphinx-js
## Why
When you write a JavaScript library, how do you explain it to people? If it's a
small project in a domain your users are familiar with, JSDoc's alphabetical
list of routines might suffice. But in a larger project, it is useful to
intersperse prose with your API docs without having to copy and paste things.
sphinx-js lets you use the industry-leading [Sphinx](https://sphinx-doc.org/)
documentation tool with JS projects. It provides a handful of directives,
patterned after the Python-centric [autodoc](https://www.sphinx-doc.org/en/latest/ext/autodoc.html) ones, for pulling
JSDoc-formatted documentation into reStructuredText pages. And, because you can
keep using JSDoc in your code, you remain compatible with the rest of your JS
tooling, like Google's Closure Compiler.
sphinx-js also works with TypeScript, using the TypeDoc tool in place of JSDoc
and emitting all the type information you would expect.
## Setup
1. Install JSDoc (or TypeDoc if you're writing TypeScript):
```bash
npm install jsdoc
```
or:
```bash
npm install typedoc@0.28
```
JSDoc 3.6.3 and 4.0.0 and TypeDoc 0.25--0.28 are known to work.
2. Install sphinx-js, which will pull in Sphinx itself as a dependency:
```bash
pip install sphinx-js
```
3. Make a documentation folder in your project by running `sphinx-quickstart`
and answering its questions:
```bash
cd my-project
sphinx-quickstart
```
```
Please enter values for the following settings (just press Enter to
accept a default value, if one is given in brackets).
Selected root path: .
You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
> Separate source and build directories (y/n) [n]:
The project name will occur in several places in the built documentation.
> Project name: My Project
> Author name(s): Fred Fredson
> Project release []: 1.0
If the documents are to be written in a language other than English,
you can select a language here by its language code. Sphinx will then
translate text that it generates into that language.
For a list of supported codes, see
https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-language.
> Project language [en]:
Selected root path: .
You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
> Separate source and build directories (y/n) [n]:
The project name will occur in several places in the built documentation.
> Project name: My Project
> Author name(s): Fred Fredson
> Project release []: 1.0
If the documents are to be written in a language other than English,
you can select a language here by its language code. Sphinx will then
translate text that it generates into that language.
For a list of supported codes, see
https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-language.
> Project language [en]:
```
4. In the generated Sphinx `conf.py` file, turn on `sphinx_js` by adding it
to `extensions`:
```python
extensions = ['sphinx_js']
```
5. If you want to document TypeScript, add:
```python
js_language = 'typescript'
```
to `conf.py` as well.
6. If your JS source code is anywhere but at the root of your project, add:
```python
js_source_path = '../somewhere/else'
```
on a line by itself in `conf.py`. The root of your JS source tree should be
where that setting points, relative to the `conf.py` file.
The default, `../`, works well when there is a `docs` folder at the root
of your project and your source code lives directly inside the root.
7. If you have special JSDoc or TypeDoc configuration, add:
```python
jsdoc_config_path = '../conf.json'
```
to `conf.py` as well.
8. If you're documenting only JS or TS and no other languages (like C), you can
set your "primary domain" to JS in `conf.py`:
```python
primary_domain = 'js'
```
The domain is `js` even if you're writing TypeScript. Then you can omit
all the "js:" prefixes in the directives below.
## History
sphinx-js was created in 2017 by Erik Rose at Mozilla. It was transferred from
Mozilla to the Pyodide organization in 2025.
## Use
In short, in a Sphinx project, use the following directives to pull in your
JSDoc documentation, then tell Sphinx to render it all by running `make html`
in your docs directory. If you have never used Sphinx or written
reStructuredText before, here is [where we left off in its tutorial](https://www.sphinx-doc.org/en/stable/tutorial.html#defining-document-structure).
For a quick start, just add things to index.rst until you prove things are
working.
### autofunction
First, document your JS code using standard JSDoc formatting:
```javascript
/**
* Return the ratio of the inline text length of the links in an element to
* the inline text length of the entire element.
*
* @param {Node} node - Types or not: either works.
* @throws {PartyError|Hearty} Multiple types work fine.
* @returns {Number} Types and descriptions are both supported.
*/
function linkDensity(node) {
const length = node.flavors.get("paragraphish").inlineLength;
const lengthWithoutLinks = inlineTextLength(
node.element,
(element) => element.tagName !== "A",
);
return (length - lengthWithoutLinks) / length;
}
```
Then, reference your documentation using sphinx-js directives. Our directives
work much like Sphinx's standard autodoc ones. You can specify just a
function's name:
```rst
.. js:autofunction:: someFunction
```
and a nicely formatted block of documentation will show up in your docs.
You can also throw in your own explicit parameter list, if you want to note
optional parameters:
```rst
.. js:autofunction:: someFunction(foo, bar[, baz])
```
Parameter properties and destructuring parameters also work fine, using
[standard JSDoc syntax](https://jsdoc.app/tags-param.html#parameters-with-properties):
```javascript
/**
* Export an image from the given canvas and save it to the disk.
*
* @param {Object} options Output options
* @param {string} options.format The output format (``jpeg``, ``png``, or
* ``webp``)
* @param {number} options.quality The output quality when format is
* ``jpeg`` or ``webp`` (from ``0.00`` to ``1.00``)
*/
function saveCanvas({ format, quality }) {
// ...
}
```
Extraction of default parameter values works as well. These act as expected,
with a few caveats:
```javascript
/**
* You must declare the params, even if you have nothing else to say, so
* JSDoc will extract the default values.
*
* @param [num]
* @param [str]
* @param [bool]
* @param [nil]
*/
function defaultsDocumentedInCode(
num = 5,
str = "true",
bool = true,
nil = null,
) {}
/**
* JSDoc guesses types for things like "42". If you have a string-typed
* default value that looks like a number or boolean, you'll need to
* specify its type explicitly. Conversely, if you have a more complex
* value like an arrow function, specify a non-string type on it so it
* isn't interpreted as a string. Finally, if you have a disjoint type like
* {string|Array} specify string first if you want your default to be
* interpreted as a string.
*
* @param {function} [func=() => 5]
* @param [str=some string]
* @param {string} [strNum=42]
* @param {string|Array} [strBool=true]
* @param [num=5]
* @param [nil=null]
*/
function defaultsDocumentedInDoclet(func, strNum, strBool, num, nil) {}
```
You can even add additional content. If you do, it will appear just below any
extracted documentation:
```rst
.. js:autofunction:: someFunction
Here are some things that will appear...
* Below
* The
* Extracted
* Docs
Enjoy!
```
`js:autofunction` has one option, `:short-name:`, which comes in handy for
chained APIs whose implementation details you want to keep out of sight. When
you use it on a class method, the containing class won't be mentioned in the
docs, the function will appear under its short name in indices, and cross
references must use the short name as well (`:func:`someFunction``):
```rst
.. js:autofunction:: someClass#someFunction
:short-name:
```
`autofunction` can also be used on callbacks defined with the [@callback tag](https://jsdoc.app/tags-callback.html).
There is experimental support for abusing `autofunction` to document
[@typedef tags](https://jsdoc.app/tags-typedef.html) as well, though the
result will be styled as a function, and `@property` tags will fall
misleadingly under an "Arguments" heading. Still, it's better than nothing
until we can do it properly.
If you are using typedoc, it also is possible to destructure keyword arguments
by using the `@destructure` tag:
```typescript
/**
* @param options
* @destructure options
*/
function f({x , y } : {
/** The x value */
x : number,
/** The y value */
y : string
}){ ... }
```
will be documented like:
```
options.x (number) The x value
options.y (number) The y value
```
### autoclass
We provide a `js:autoclass` directive which documents a class with the
concatenation of its class comment and its constructor comment. It shares all
the features of `js:autofunction` and even takes the same `:short-name:`
flag, which can come in handy for inner classes. The easiest way to use it is
to invoke its `:members:` option, which automatically documents all your
class's public methods and attributes:
```rst
.. js:autoclass:: SomeEs6Class(constructor, args, if, you[, wish])
:members:
```
You can add private members by saying:
```rst
.. js:autoclass:: SomeEs6Class
:members:
:private-members:
```
Privacy is determined by JSDoc `@private` tags or TypeScript's `private`
keyword.
Exclude certain members by name with `:exclude-members:`:
```rst
.. js:autoclass:: SomeEs6Class
:members:
:exclude-members: Foo, bar, baz
```
Or explicitly list the members you want. We will respect your ordering.
```rst
.. js:autoclass:: SomeEs6Class
:members: Qux, qum
```
When explicitly listing members, you can include `*` to include all
unmentioned members. This is useful to have control over ordering of some
elements, without having to include an exhaustive list.
```rst
.. js:autoclass:: SomeEs6Class
:members: importMethod, *, uncommonlyUsedMethod
```
Finally, if you want full control, pull your class members in one at a time by
embedding `js:autofunction` or `js:autoattribute`:
```rst
.. js:autoclass:: SomeEs6Class
.. js:autofunction:: SomeEs6Class#someMethod
Additional content can go here and appears below the in-code comments,
allowing you to intersperse long prose passages and examples that you
don't want in your code.
```
### autoattribute
This is useful for documenting public properties:
```javascript
class Fnode {
constructor(element) {
/**
* The raw DOM element this wrapper describes
*/
this.element = element;
}
}
```
And then, in the docs:
```rst
.. autoclass:: Fnode
.. autoattribute:: Fnode#element
```
This is also the way to document ES6-style getters and setters, as it omits the
trailing `()` of a function. The assumed practice is the usual JSDoc one:
document only one of your getter/setter pair:
```javascript
class Bing {
/** The bong of the bing */
get bong() {
return this._bong;
}
set bong(newBong) {
this._bong = newBong * 2;
}
}
```
And then, in the docs:
```rst
.. autoattribute:: Bing#bong
```
### automodule
This directive documents all exports on a module. For example:
```rst
.. js:automodule:: package.submodule
```
### autosummary
This directive should be paired with an automodule directive (which may occur in
a distinct rst file). It makes a summary table with links to the entries
generated by the automodule directive. Usage:
```rst
.. js:automodule:: package.submodule
```
## Dodging Ambiguity With Pathnames
If you have same-named objects in different files, use pathnames to
disambiguate them. Here's a particularly long example:
```rst
.. js:autofunction:: ./some/dir/some/file.SomeClass#someInstanceMethod.staticMethod~innerMember
```
You may recognize the separators `#.~` from [JSDoc namepaths](https://jsdoc.app/about-namepaths.html); they work the same here.
For conciseness, you can use any unique suffix, as long as it consists of
complete path segments. These would all be equivalent to the above, assuming
they are unique within your source tree:
```
innerMember
staticMethod~innerMember
SomeClass#someInstanceMethod.staticMethod~innerMember
some/file.SomeClass#someInstanceMethod.staticMethod~innerMember
```
Things to note:
- We use simple file paths rather than JSDoc's `module:` prefix or TypeDoc's
`external:` or `module:` ones.
- We use simple backslash escaping exclusively rather than switching escaping
schemes halfway through the path; JSDoc itself [is headed that way as well](https://github.com/jsdoc3/jsdoc/issues/876). The characters that need to
be escaped are `#.~(/`, though you do not need to escape the dots in a
leading `./` or `../`. A really horrible path might be:
```
some/path\ with\ spaces/file.topLevelObject#instanceMember.staticMember\(with\(parens
```
- Relative paths are relative to the `js_source_path` specified in the
config. Absolute paths are not allowed.
Behind the scenes, sphinx-js will change all separators to dots so that:
- Sphinx's "shortening" syntax works: ":func:\`~InwardRhs.atMost\`" prints as
merely`atMost()`. (For now, you should always use dots rather than other
namepath separators: `#~`.)
- Sphinx indexes more informatively, saying methods belong to their classes.
## Saving Keystrokes By Setting The Primary Domain
To save some keystrokes, you can set:
```python
primary_domain = 'js'
```
in `conf.py` and then use `autofunction` rather than `js:autofunction`.
## TypeScript: Getting Superclass and Interface Links To Work
To have a class link to its superclasses and implemented interfaces, you'll
need to document the superclass (or interface) somewhere using `js:autoclass`
or `js:class` and use the class's full (but dotted) path when you do:
```rst
.. js:autoclass:: someFile.SomeClass
```
Unfortunately, Sphinx's `~` syntax doesn't work in these spots, so users will
see the full paths in the documentation.
## TypeScript: Cross references
TypeScript types will be converted to cross references. To render cross
references, you can define a hook in `conf.py` called `ts_type_xref_formatter`. It
should take two arguments: the first argument is the sphinx confix, and the
second is an `sphinx_js.ir.TypeXRef` object. This has a `name` field and two
variants:
- a `sphinx_js.ir.TypeXRefInternal` with fields `path` and `kind`
- a `sphinx_js.ir.TypeXRefExternal` with fields `name`, `package`,
`sourcefilename` and `qualifiedName`
The return value should be restructured text that you wish to be inserted in
place of the type. For example:
```python
def ts_xref_formatter(config, xref):
if isinstance(xref, TypeXRefInternal):
name = rst.escape(xref.name)
return f":js:{xref.kind}:`{name}`"
else:
# Otherwise, don't insert a xref
return xref.name
```
## Configuration Reference
### `js_language`
Use 'javascript' or 'typescript' depending on the language you use. The
default is 'javascript'.
### `js_source_path`
A list of directories to scan (non-recursively) for JS or TS source files,
relative to Sphinx's conf.py file. Can be a string instead if there is only
one. If there is more than one, `root_for_relative_js_paths` must be
specified as well. Defaults to `../`.
### `root_for_relative_js_paths`
Relative JS entity paths are resolved relative to this path. Defaults to
`js_source_path` if not present.
### `jsdoc_config_path`
A conf.py-relative path to a JSDoc config file, which is useful if you want to
specify your own JSDoc options, like recursion and custom filename matching.
If using TypeDoc, you can also point to a `typedoc.json` file.
### `jsdoc_tsconfig_path`
If using TypeDoc, specify the path of `tsconfig.json` file
### `ts_type_xref_formatter`
A function for formatting TypeScript type cross references. See the
"TypeScript: Cross references" section below.
### `ts_type_bold`
Make all TypeScript types bold if `true`.
### `ts_sphinx_js_config`
A link to a TypeScript config file.
## The `ts_sphinx_js_config` file
This file should be a TypeScript module. It's executed in a context where it can
import `typedoc` and `sphinx_js`. These functions take TypeDoc IR objects as
arguments. Since the TypeDoc IR is unstable, this config may often break when
switching TypeDoc versions. However, these hooks are very powerful so using them
may be worthwhile anyways. This API is experimental and may change in the
future.
For an example, you can see Pyodide's config file [here](shouldDestructureArg).
This file should export a config object with some of the three following
functions:
- `shouldDestructureArg: (param: ParameterReflection) => boolean`
This function takes a `ParameterReflection` and decides if it should be
destructured. If so, it's equivalent to putting a `@destructure` tag for the
argument. For example:
```typescript
function shouldDestructureArg(param: ParameterReflection) {
return param.name === "options";
}
```
- `preConvert?: (app: Application) => Promise<void>;`
This hook is called with the TypeDoc application as argument before the
TypeScript files are parsed. For example, it can be used to add extra TypeDoc
plugins.
- `postConvert: (app: Application, project: ProjectReflection, typeDocToIRMap: Map<DeclarationReflection, TopLevelIR>) => void`
This hook is called after the sphinx_js IR is created. It can be used to
modify the IR arbitrarily. It is very experimental and subject to breaking
changes.
For example, this `postConvert` hook removes the constructor from classes marked with
`@hideconstructor`.
```typescript
function postConvert(app, project, typeDocToIRMap) {
for (const [key, value] of typeDocToIRMap.entries()) {
if (
value.kind === "class" &&
value.modifier_tags.includes("@hideconstructor")
) {
value.constructor_ = null;
}
}
}
```
To use it, you'll also need to add a tag definition for `@hideconstructor` to your `tsdoc.json` file:
```json
{
"tagDefinitions": [
{
"tagName": "@hideconstructor",
"syntaxKind": "modifier"
}
]
}
```
This `postConvert` hook hides external attributes and functions from the documentation:
```typescript
function postConvert(app, project, typeDocToIRMap) {
for (const [key, value] of typeDocToIRMap.entries()) {
if (value.kind === "attribute" || value.kind === "function") {
value.is_private = key.flags.isExternal || key.flags.isPrivate;
}
}
}
```
## How sphinx-js finds typedoc / jsdoc
1. If the environment variable `SPHINX_JS_NODE_MODULES` is defined, it is
expected to point to a `node_modules` folder in which typedoc / jsdoc is installed.
2. If `SPHINX_JS_NODE_MODULES` is not defined, we look in the directory of
`conf.py` for a `node_modules` folder in which typedoc / jsdoc. If this is
not found, we look for a `node_modules` folder in the parent directories
until we make it to the root of the file system.
3. We check if `typedoc` / `jsdoc` are on the PATH, if so we use that.
4. If none of the previous approaches located `typedoc` / `jsdoc` we raise an error.
## Example
A good example using most of sphinx-js's functionality is the Fathom
documentation. A particularly juicy page is
<https://mozilla.github.io/fathom/ruleset.html>. Click the "View page
source" link to see the raw directives.
For a TypeScript example, see [the Pyodide api docs](https://pyodide.org/en/stable/usage/api/js-api.html).
[ReadTheDocs](https://readthedocs.org/) is the canonical hosting platform for
Sphinx docs and now supports sphinx-js. Put this in the
`.readthedocs.yml` file at the root of your repo:
```yaml
python:
install:
- requirements: docs/requirements.txt
```
Then put the version of sphinx-js you want in `docs/requirements.txt`. For
example:
```
sphinx-js==3.1.2
```
## Caveats
- We don't understand the inline JSDoc constructs like `{@link foo}`; you
have to use Sphinx-style equivalents for now, like `:js:func:`foo`` (or
simply `:func:`foo`` if you have set `primary_domain = 'js'` in conf.py.
- So far, we understand and convert the JSDoc block tags `@param`,
`@returns`, `@throws`, `@example` (without the optional `<caption>`),
`@deprecated`, `@see`, and their synonyms. Other ones will go _poof_ into
the ether.
## Tests
Run the tests using nox, which will also install JSDoc and TypeDoc at pinned
versions:
```bash
pip install nox
nox
```
## Provenance
sphinx-js was originally written and maintained by Erik Rose and various
contributors within and without the Mozilla Corporation and Foundation.
See `CONTRIBUTORS` for details.
================================================
FILE: biome.json
================================================
{
"$schema": "https://biomejs.dev/schemas/2.1.1/schema.json",
"vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
"files": {
"includes": [
"**/*",
"!**/build/**",
"!**/.mypy_cache/**",
"!**/.vscode/**"
]
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 80,
"attributePosition": "auto",
"bracketSameLine": false,
"bracketSpacing": true,
"expand": "auto",
"useEditorconfig": true
},
"linter": { "enabled": false, "rules": { "recommended": true } },
"javascript": {
"formatter": {
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "always",
"arrowParentheses": "always",
"bracketSameLine": false,
"quoteStyle": "double",
"attributePosition": "auto",
"bracketSpacing": true
}
},
"html": { "formatter": { "selfCloseVoidElements": "always" } },
"assist": {
"enabled": false,
"actions": { "source": { "organizeImports": "on" } }
}
}
================================================
FILE: noxfile.py
================================================
from pathlib import Path
from textwrap import dedent
import nox
from nox.sessions import Session
PROJECT_ROOT = Path(__file__).parent
@nox.session(python=["3.10", "3.11", "3.12", "3.13", "3.14"])
def tests(session: Session) -> None:
session.install(".[test]")
venvroot = Path(session.bin).parent
(venvroot / "node_modules").mkdir()
with session.chdir(venvroot):
session.run(
"npm", "i", "--no-save", "jsdoc@4.0.0", "typedoc@0.25", external=True
)
session.run(
"pytest",
"--junitxml=test-results.xml",
"--cov=sphinx_js",
"--cov-report",
"xml",
)
def typecheck_ts(session: Session, typedoc: str) -> None:
if typedoc == "0.26":
# Upstream type errors here =(
return
# Typecheck
with session.chdir("sphinx_js/js"):
session.run("npm", "i", f"typedoc@{typedoc}", external=True)
session.run("npm", "i", external=True)
session.run("npx", "tsc", external=True)
@nox.session(python=["3.12"])
@nox.parametrize("typedoc", ["0.25", "0.26", "0.27", "0.28"])
def test_typedoc(session: Session, typedoc: str) -> None:
typecheck_ts(session, typedoc)
# Install python dependencies
session.install(".[test]")
venvroot = Path(session.bin).parent
node_modules = (venvroot / "node_modules").resolve()
node_modules.mkdir()
with session.chdir(venvroot):
# Install node dependencies
session.run(
"npm",
"i",
"--no-save",
"tsx",
"jsdoc@4.0.0",
f"typedoc@{typedoc}",
external=True,
)
# Run typescript tests
test_file = (PROJECT_ROOT / "tests/test.ts").resolve()
register_import_hook = PROJECT_ROOT / "sphinx_js/js/registerImportHook.mjs"
ts_tests = Path(venvroot / "ts_tests")
# Write script to a file so that it is easy to rerun without reinstalling dependencies.
ts_tests.write_text(
dedent(
f"""\
#!/bin/sh
npx typedoc --version
TYPEDOC_NODE_MODULES={venvroot} node --import {register_import_hook} --import {node_modules / "tsx/dist/loader.mjs"} --test {test_file}
"""
)
)
ts_tests.chmod(0o777)
session.run(ts_tests, external=True)
# Run Python tests
session.run("pytest", "--junitxml=test-results.xml", "-k", "not js")
@nox.session(python=["3.12"])
def test_sphinx_6(session: Session) -> None:
session.install("sphinx<7")
session.install(".[test]")
venvroot = Path(session.bin).parent
(venvroot / "node_modules").mkdir()
with session.chdir(venvroot):
session.run(
"npm", "i", "--no-save", "jsdoc@4.0.0", "typedoc@0.25", external=True
)
session.run("pytest", "--junitxml=test-results.xml", "-k", "not js")
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[project]
name = "sphinx-js"
description = "Support for using Sphinx on JSDoc-documented JS code"
readme = "README.md"
license = {text = "MIT"}
authors = [
{name = "Hood Chatham", email = "roberthoodchatham@gmail.com"},
]
requires-python = ">=3.10"
dependencies = [
"attrs",
"cattrs<25.1",
"Jinja2>2.0",
"markupsafe",
"parsimonious>=0.10.0,<0.11.0",
"Sphinx>=4.1.0",
]
keywords = [
"docs",
"documentation",
"javascript",
"js",
"jsdoc",
"restructured",
"sphinx",
"typedoc",
"typescript",
]
classifiers = [
"Framework :: Sphinx :: Domain",
"Framework :: Sphinx :: Extension",
"Intended Audience :: Developers",
"Natural Language :: English",
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Topic :: Documentation :: Sphinx",
"Topic :: Software Development :: Documentation",
"Typing :: Typed",
]
dynamic = ["version"]
[project.urls]
Homepage = "https://github.com/pyodide/sphinx-js"
[project.optional-dependencies]
test = [
"beautifulsoup4",
"build",
"defusedxml",
"nox",
"pytest-cov",
"recommonmark",
"twine",
]
[tool.hatch.version]
source = "vcs"
[tool.hatch.build.targets.sdist]
include = [
"/sphinx_js",
"/tests",
"/LICENSE",
"/requirements_dev.txt",
"/noxfile.py",
"/README.md",
]
[tool.hatch.build.targets.wheel]
packages = ["sphinx_js"]
[tool.mypy]
python_version = "3.10"
show_error_codes = true
warn_unreachable = true
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
# Strict checks
warn_unused_configs = true
check_untyped_defs = true
disallow_any_generics = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_return_any = true
no_implicit_reexport = true
strict_equality = true
[[tool.mypy.overrides]]
module = "sphinx_js.parsers"
disallow_untyped_defs = false
disallow_untyped_calls = false
[tool.ruff]
lint.select = [
"E", # pycodestyles
"W", # pycodestyles
"F", # pyflakes
"B0", # bugbear (all B0* checks enabled by default)
"B904", # bugbear (Within an except clause, raise exceptions with raise ... from err)
"B905", # bugbear (zip() without an explicit strict= parameter set.)
"UP", # pyupgrade
"I", # isort
"PGH", # pygrep-hooks
]
lint.ignore = ["E402", "E501", "E731", "E741", "B904", "B020", "UP031"]
target-version = "py310"
================================================
FILE: sphinx_js/__init__.py
================================================
from os.path import join, normpath
from pathlib import Path
from textwrap import dedent
from typing import Any
from sphinx.application import Sphinx
from sphinx.errors import SphinxError
from .directives import (
add_directives,
)
from .jsdoc import Analyzer as JsAnalyzer
from .typedoc import Analyzer as TsAnalyzer
SPHINX_JS_CSS = "sphinx_js.css"
def make_css_file(app: Sphinx) -> None:
dst = Path(app.outdir) / "_static" / SPHINX_JS_CSS
text = ""
if app.config.ts_type_bold:
text = dedent(
"""\
.sphinx_js-type {
font-weight: bolder;
}
"""
)
dst.write_text(text)
def on_build_finished(app: Sphinx, exc: Exception | None) -> None:
if exc or app.builder.format != "html":
return
make_css_file(app)
def setup(app: Sphinx) -> None:
app.setup_extension("sphinx.ext.autosummary")
# I believe this is the best place to run jsdoc. I was tempted to use
# app.add_source_parser(), but I think the kind of source it's referring to
# is RSTs.
app.connect("builder-inited", analyze)
add_directives(app)
# TODO: We could add a js:module with app.add_directive_to_domain().
app.add_config_value("js_language", default="javascript", rebuild="env")
app.add_config_value(
"js_source_path", default=["../"], rebuild="env", types=[str, list]
)
# We could use a callable as the "default" param here, but then we would
# have had to duplicate or build framework around the logic that promotes
# js_source_path to a list and calls abspath() on it. It's simpler this way
# until we need to access js_source_path from more than one place.
app.add_config_value("root_for_relative_js_paths", None, "env")
app.add_config_value("jsdoc_config_path", default=None, rebuild="env")
app.add_config_value("jsdoc_tsconfig_path", default=None, rebuild="env")
app.add_config_value("ts_type_xref_formatter", None, "env")
app.add_config_value("ts_type_bold", False, "env")
app.add_config_value("ts_sphinx_js_config", None, "env")
app.add_css_file(SPHINX_JS_CSS)
app.connect("build-finished", on_build_finished)
def analyze(app: Sphinx) -> None:
"""Run JSDoc or another analysis tool across a whole codebase, and squirrel
away its results in a language-specific Analyzer."""
# Normalize config values:
source_paths = (
[app.config.js_source_path]
if isinstance(app.config.js_source_path, str)
else app.config.js_source_path
)
abs_source_paths = [normpath(join(app.confdir, path)) for path in source_paths]
root_for_relative_paths = root_or_fallback(
normpath(join(app.confdir, app.config.root_for_relative_js_paths))
if app.config.root_for_relative_js_paths
else None,
abs_source_paths,
)
# Pick analyzer:
try:
analyzer: Any = {"javascript": JsAnalyzer, "typescript": TsAnalyzer}[
app.config.js_language
]
except KeyError:
raise SphinxError(
"Unsupported value of js_language in config: %s" % app.config.js_language
)
# Analyze source code:
app._sphinxjs_analyzer = analyzer.from_disk( # type:ignore[attr-defined]
abs_source_paths, app, root_for_relative_paths
)
def root_or_fallback(
root_for_relative_paths: str | None, abs_source_paths: list[str]
) -> str:
"""Return the path that relative JS entity paths in the docs are relative to.
Fall back to the sole JS source path if the setting is unspecified.
:arg root_for_relative_paths: The absolute-ized root_for_relative_js_paths
setting. None if the user hasn't specified it.
:arg abs_source_paths: Absolute paths of dirs to scan for JS code
"""
if root_for_relative_paths:
return root_for_relative_paths
else:
if len(abs_source_paths) > 1:
raise SphinxError(
"Since more than one js_source_path is specified in conf.py, root_for_relative_js_paths must also be specified. This allows paths beginning with ./ or ../ to be unambiguous."
)
else:
return abs_source_paths[0]
================================================
FILE: sphinx_js/analyzer_utils.py
================================================
"""Conveniences shared among analyzers"""
import os
import shutil
from collections.abc import Callable, Sequence
from functools import cache, wraps
from json import dump, load
from pathlib import Path
from typing import Any, ParamSpec, TypeVar
from sphinx.errors import SphinxError
def program_name_on_this_platform(program: str) -> str:
"""Return the name of the executable file on the current platform, given a
command name with no extension."""
return program + ".cmd" if os.name == "nt" else program
@cache
def search_node_modules(cmdname: str, cmdpath: str, dir: str | Path) -> str:
if "SPHINX_JS_NODE_MODULES" in os.environ:
return str(Path(os.environ["SPHINX_JS_NODE_MODULES"]) / cmdpath)
# We want to include "curdir" in parent_dirs, so add a garbage suffix
parent_dirs = (Path(dir) / "garbage").parents
# search for local install
for base in parent_dirs:
typedoc = base / "node_modules" / cmdpath
if typedoc.is_file():
return str(typedoc.resolve())
# perhaps it's globally installed
result = shutil.which(cmdname)
if result:
return str(Path(result).resolve())
raise SphinxError(
f'{cmdname} was not found. Install it using "npm install {cmdname}".'
)
class Command:
def __init__(self, program: str):
self.program = program_name_on_this_platform(program)
self.args: list[str] = []
def add(self, *args: str) -> None:
self.args.extend(args)
def make(self) -> list[str]:
return [self.program] + self.args
T = TypeVar("T")
P = ParamSpec("P")
def cache_to_file(
get_filename: Callable[..., str | None],
) -> Callable[[Callable[P, T]], Callable[P, T]]:
"""Return a decorator that will cache the result of ``get_filename()`` to a
file
:arg get_filename: A function which receives the original arguments of the
decorated function
"""
def decorator(fn: Callable[P, T]) -> Callable[P, T]:
@wraps(fn)
def decorated(*args: P.args, **kwargs: P.kwargs) -> Any:
filename = get_filename(*args, **kwargs)
if filename and os.path.isfile(filename):
with open(filename, encoding="utf-8") as f:
return load(f)
res = fn(*args, **kwargs)
if filename:
with open(filename, "w", encoding="utf-8") as f:
dump(res, f, indent=2)
return res
return decorated
return decorator
def is_explicitly_rooted(path: str) -> bool:
"""Return whether a relative path is explicitly rooted relative to the
cwd, rather than starting off immediately with a file or folder name.
It's nice to have paths start with "./" (or "../", "../../", etc.) so, if a
user is that explicit, we still find the path in the suffix tree.
"""
return path.startswith(("../", "./")) or path in ("..", ".")
def dotted_path(segments: Sequence[str]) -> str:
"""Convert a JS object path (``['dir/', 'file/', 'class#',
'instanceMethod']``) to a dotted style that Sphinx will better index.
Strip off any leading relative-dir segments (./ or ../) because they lead
to invalid paths like ".....foo". Ultimately, we should thread ``base_dir``
into this and construct a full path based on that.
"""
if not segments:
return ""
segments_without_separators = [
s[:-1] for s in segments[:-1] if s not in ["./", "../"]
]
segments_without_separators.append(segments[-1])
return ".".join(segments_without_separators)
================================================
FILE: sphinx_js/directives.py
================================================
"""These are the actual Sphinx directives we provide, but they are skeletal.
The real meat is in their parallel renderer classes, in renderers.py. The split
is due to the unfortunate trick we need here of having functions return the
directive classes after providing them the ``app`` symbol, where we store the
JSDoc output, via closure. The renderer classes, able to be top-level classes,
can access each other and collaborate.
"""
import re
from collections.abc import Iterable
from functools import cache
from os.path import join, relpath
from typing import Any, cast
from docutils import nodes
from docutils.nodes import Node
from docutils.parsers.rst import Directive
from docutils.parsers.rst import Parser as RstParser
from docutils.parsers.rst.directives import flag
from sphinx import addnodes
from sphinx.addnodes import desc_signature
from sphinx.application import Sphinx
from sphinx.domains import ObjType, javascript
from sphinx.domains.javascript import (
JavaScriptDomain,
JSCallable,
JSConstructor,
JSObject,
JSXRefRole,
)
from sphinx.locale import _
from sphinx.util.docfields import GroupedField, TypedField
from sphinx.writers.html5 import HTML5Translator
from sphinx.writers.latex import LaTeXTranslator
from sphinx.writers.text import TextTranslator
from .renderers import (
AutoAttributeRenderer,
AutoClassRenderer,
AutoFunctionRenderer,
AutoModuleRenderer,
AutoSummaryRenderer,
Renderer,
new_document_from_parent,
)
def unescape(escaped: str) -> str:
# For some reason the string we get has a bunch of null bytes in it??
# Remove them...
escaped = escaped.replace("\x00", "")
# For some reason the extra slash before spaces gets lost between the .rst
# source and when this directive is called. So don't replace "\<space>" =>
# "<space>"
return re.sub(r"\\([^ ])", r"\1", escaped)
def _members_to_exclude(arg: str | None) -> set[str]:
"""Return a set of members to exclude given a comma-delim list of them.
Exclude none if none are passed. This differs from autodocs' behavior,
which excludes all. That seemed useless to me.
"""
return set(a.strip() for a in (arg or "").split(","))
def sphinx_js_type_role( # type: ignore[no-untyped-def]
role,
rawtext,
text,
lineno,
inliner,
options=None,
content=None,
):
"""
The body should be escaped rst. This renders its body as rst and wraps the
result in <span class="sphinx_js-type"> </span>
"""
unescaped = unescape(text)
parent_doc = inliner.document
source = parent_doc.get("source", "")
# Get line number stored by new_document_from_parent in rst_nodes if we can
# find it, otherwise use lineno of directive.
line = getattr(parent_doc, "sphinx_js_source_line", None) or lineno
doc = new_document_from_parent(source, parent_doc)
# Prepend newlines so errors report correct line number
padded = "\n" * (line - 1) + unescaped
RstParser().parse(padded, doc)
n = nodes.inline(text)
n["classes"].append("sphinx_js-type")
n += doc.children[0].children
return [n], []
class JSXrefMixin:
def make_xref(
self,
rolename: Any,
domain: Any,
target: Any,
innernode: Any = nodes.emphasis,
contnode: Any = None,
env: Any = None,
inliner: Any = None,
location: Any = None,
) -> Any:
# Set inliner to None just like the PythonXrefMixin does so the
# xref doesn't get rendered as a function.
return super().make_xref( # type:ignore[misc]
rolename,
domain,
target,
innernode,
contnode,
env,
inliner=None,
location=None,
)
class JSTypedField(JSXrefMixin, TypedField):
pass
class JSGroupedField(JSXrefMixin, GroupedField):
pass
# Cache this to guarantee it only runs once.
@cache
def fix_js_make_xref() -> None:
"""Monkeypatch to fix sphinx.domains.javascript TypedField and GroupedField
Fixes https://github.com/sphinx-doc/sphinx/issues/11021
"""
# Replace javascript module
javascript.TypedField = JSTypedField # type:ignore[attr-defined]
javascript.GroupedField = JSGroupedField # type:ignore[attr-defined]
# Fix the one place TypedField and GroupedField are used in the javascript
# module
javascript.JSCallable.doc_field_types = [
JSTypedField(
"arguments",
label=_("Arguments"),
names=("argument", "arg", "parameter", "param"),
typerolename="func",
typenames=("paramtype", "type"),
),
JSGroupedField(
"errors",
label=_("Throws"),
rolename="func",
names=("throws",),
can_collapse=True,
),
] + javascript.JSCallable.doc_field_types[2:]
# Cache this to guarantee it only runs once.
@cache
def fix_staticfunction_objtype() -> None:
"""Override js:function directive with one that understands static and async
prefixes
"""
JavaScriptDomain.directives["function"] = JSFunction
@cache
def add_type_param_field_to_directives() -> None:
typeparam_field = JSGroupedField(
"typeparam",
label="Type parameters",
rolename="func",
names=("typeparam",),
can_collapse=True,
)
JSCallable.doc_field_types.insert(0, typeparam_field)
JSConstructor.doc_field_types.insert(0, typeparam_field)
class JsDirective(Directive):
"""Abstract directive which knows how to pull things out of our IR"""
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {"short-name": flag}
def _run(self, renderer_class: type[Renderer], app: Sphinx) -> list[Node]:
renderer = renderer_class.from_directive(self, app)
note_dependencies(app, renderer.dependencies())
return renderer.rst_nodes()
class JsDirectiveWithChildren(JsDirective):
option_spec = JsDirective.option_spec.copy()
option_spec.update(
{
"members": lambda members: (
[m.strip() for m in members.split(",")] if members else []
),
"exclude-members": _members_to_exclude,
"private-members": flag,
}
)
def note_dependencies(app: Sphinx, dependencies: Iterable[str]) -> None:
"""Note dependencies of current document.
:arg app: Sphinx application object
:arg dependencies: iterable of filename strings relative to root_for_relative_paths
"""
for fn in dependencies:
# Dependencies in the IR are relative to `root_for_relative_paths`, itself
# relative to the configuration directory.
analyzer = app._sphinxjs_analyzer # type:ignore[attr-defined]
abs = join(analyzer._base_dir, fn)
# Sphinx dependencies are relative to the source directory.
rel = relpath(abs, app.srcdir)
app.env.note_dependency(rel)
def auto_function_directive_bound_to_app(app: Sphinx) -> type[Directive]:
class AutoFunctionDirective(JsDirective):
"""js:autofunction directive, which spits out a js:function directive
Takes a single argument which is a JS function name combined with an
optional formal parameter list, all mashed together in a single string.
"""
def run(self) -> list[Node]:
return self._run(AutoFunctionRenderer, app)
return AutoFunctionDirective
def auto_class_directive_bound_to_app(app: Sphinx) -> type[Directive]:
class AutoClassDirective(JsDirectiveWithChildren):
"""js:autoclass directive, which spits out a js:class directive
Takes a single argument which is a JS class name combined with an
optional formal parameter list for the constructor, all mashed together
in a single string.
"""
def run(self) -> list[Node]:
return self._run(AutoClassRenderer, app)
return AutoClassDirective
def auto_attribute_directive_bound_to_app(app: Sphinx) -> type[Directive]:
class AutoAttributeDirective(JsDirective):
"""js:autoattribute directive, which spits out a js:attribute directive
Takes a single argument which is a JS attribute name.
"""
def run(self) -> list[Node]:
return self._run(AutoAttributeRenderer, app)
return AutoAttributeDirective
class desc_js_type_parameter_list(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a javascript type parameter list.
Unlike normal parameter lists, we use angle braces <> as the braces. Based
on sphinx.addnodes.desc_type_parameter_list
"""
child_text_separator = ", "
def astext(self) -> str:
return f"<{nodes.FixedTextElement.astext(self)}>"
def html5_visit_desc_js_type_parameter_list(
self: HTML5Translator, node: nodes.Element
) -> None:
"""Define the html/text rendering for desc_js_type_parameter_list. Based on
sphinx.writers.html5.visit_desc_type_parameter_list
"""
if hasattr(self, "_visit_sig_parameter_list"):
# Sphinx 7
return self._visit_sig_parameter_list(node, addnodes.desc_parameter, "<", ">")
# Sphinx <7
self.body.append('<span class="sig-paren"><</span>')
self.first_param = 1 # type:ignore[attr-defined]
self.optional_param_level = 0
# How many required parameters are left.
self.required_params_left = sum(
[isinstance(c, addnodes.desc_parameter) for c in node.children]
)
self.param_separator = node.child_text_separator
def html5_depart_desc_js_type_parameter_list(
self: HTML5Translator, node: nodes.Element
) -> None:
"""Define the html/text rendering for desc_js_type_parameter_list. Based on
sphinx.writers.html5.depart_desc_type_parameter_list
"""
if hasattr(self, "_depart_sig_parameter_list"):
# Sphinx 7
return self._depart_sig_parameter_list(node)
# Sphinx <7
self.body.append('<span class="sig-paren">></span>')
def text_visit_desc_js_type_parameter_list(
self: TextTranslator, node: nodes.Element
) -> None:
if hasattr(self, "_visit_sig_parameter_list"):
# Sphinx 7
return self._visit_sig_parameter_list(node, addnodes.desc_parameter, "<", ">")
# Sphinx <7
self.add_text("<")
self.first_param = 1 # type:ignore[attr-defined]
def text_depart_desc_js_type_parameter_list(
self: TextTranslator, node: nodes.Element
) -> None:
if hasattr(self, "_depart_sig_parameter_list"):
# Sphinx 7
return self._depart_sig_parameter_list(node)
# Sphinx <7
self.add_text(">")
def latex_visit_desc_type_parameter_list(
self: LaTeXTranslator, node: nodes.Element
) -> None:
pass
def latex_depart_desc_type_parameter_list(
self: LaTeXTranslator, node: nodes.Element
) -> None:
pass
def add_param_list_to_signode(signode: desc_signature, params: str) -> None:
paramlist = desc_js_type_parameter_list()
for arg in params.split(","):
paramlist += addnodes.desc_parameter("", "", addnodes.desc_sig_name(arg, arg))
signode += paramlist
def handle_typeparams_for_signature(
self: JSObject, sig: str, signode: desc_signature, *, keep_callsig: bool
) -> tuple[str, str]:
"""Generic function to handle type params in the sig line for interfaces,
classes, and functions.
For interfaces and classes we don't prefer the look with parentheses so we
also remove them (by setting keep_callsig to False).
"""
typeparams = None
if "<" in sig and ">" in sig:
base, _, rest = sig.partition("<")
typeparams, _, params = rest.partition(">")
sig = base + params
res = JSCallable.handle_signature(cast(JSCallable, self), sig, signode)
sig = sig.strip()
lastchild = None
# Check for call signature, if present take it off
if signode.children[-1].astext().endswith(")"):
lastchild = signode.children[-1]
signode.remove(lastchild)
if typeparams:
add_param_list_to_signode(signode, typeparams)
# if we took off a call signature and we want to keep it put it back.
if keep_callsig and lastchild:
signode += lastchild
return res
class JSFunction(JSCallable):
"""Variant of JSCallable that can take static/async prefixes"""
option_spec = {
**JSCallable.option_spec,
"static": flag,
"async": flag,
}
def get_display_prefix(
self,
) -> list[Any]:
result = []
for name in ["static", "async"]:
if name in self.options:
result.extend(
[
addnodes.desc_sig_keyword(name, name),
addnodes.desc_sig_space(),
]
)
return result
def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
return handle_typeparams_for_signature(self, sig, signode, keep_callsig=True)
class JSInterface(JSCallable):
"""An interface directive.
Based on sphinx.domains.javascript.JSConstructor.
"""
allow_nesting = True
def get_display_prefix(self) -> list[Node]:
return [
addnodes.desc_sig_keyword("interface", "interface"),
addnodes.desc_sig_space(),
]
def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
return handle_typeparams_for_signature(self, sig, signode, keep_callsig=False)
class JSTypeAlias(JSObject):
doc_field_types = [
JSGroupedField(
"typeparam",
label="Type parameters",
names=("typeparam",),
can_collapse=True,
)
]
def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
return handle_typeparams_for_signature(self, sig, signode, keep_callsig=False)
class JSClass(JSConstructor):
def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
return handle_typeparams_for_signature(self, sig, signode, keep_callsig=True)
@cache
def patch_JsObject_get_index_text() -> None:
"""Add our additional object types to the index"""
orig_get_index_text = JSObject.get_index_text
def patched_get_index_text(
self: JSObject, objectname: str, name_obj: tuple[str, str]
) -> str:
name, obj = name_obj
if self.objtype == "interface":
return _("%s() (interface)") % name
return orig_get_index_text(self, objectname, name_obj)
JSObject.get_index_text = patched_get_index_text # type:ignore[method-assign]
def auto_module_directive_bound_to_app(app: Sphinx) -> type[Directive]:
class AutoModuleDirective(JsDirectiveWithChildren):
required_arguments = 1
def run(self) -> list[Node]:
return self._run(AutoModuleRenderer, app)
return AutoModuleDirective
def auto_summary_directive_bound_to_app(app: Sphinx) -> type[Directive]:
class JsDocSummary(JsDirective):
required_arguments = 1
def run(self) -> list[Node]:
return self._run(AutoSummaryRenderer, app)
return JsDocSummary
def add_directives(app: Sphinx) -> None:
fix_js_make_xref()
fix_staticfunction_objtype()
add_type_param_field_to_directives()
patch_JsObject_get_index_text()
app.add_role("sphinx_js_type", sphinx_js_type_role)
app.add_directive_to_domain(
"js", "autofunction", auto_function_directive_bound_to_app(app)
)
app.add_directive_to_domain(
"js", "autoclass", auto_class_directive_bound_to_app(app)
)
app.add_directive_to_domain(
"js", "autoattribute", auto_attribute_directive_bound_to_app(app)
)
app.add_directive_to_domain(
"js", "automodule", auto_module_directive_bound_to_app(app)
)
app.add_directive_to_domain(
"js", "autosummary", auto_summary_directive_bound_to_app(app)
)
app.add_directive_to_domain("js", "class", JSClass)
app.add_role_to_domain("js", "class", JSXRefRole())
JavaScriptDomain.object_types["interface"] = ObjType(_("interface"), "interface")
app.add_directive_to_domain("js", "interface", JSInterface)
app.add_role_to_domain("js", "interface", JSXRefRole())
JavaScriptDomain.object_types["typealias"] = ObjType(_("type alias"), "typealias")
app.add_directive_to_domain("js", "typealias", JSTypeAlias)
app.add_role_to_domain("js", "typealias", JSXRefRole())
app.add_node(
desc_js_type_parameter_list,
html=(
html5_visit_desc_js_type_parameter_list,
html5_depart_desc_js_type_parameter_list,
),
text=(
text_visit_desc_js_type_parameter_list,
text_depart_desc_js_type_parameter_list,
),
latex=(
latex_visit_desc_type_parameter_list,
latex_depart_desc_type_parameter_list,
),
)
================================================
FILE: sphinx_js/ir.py
================================================
"""Intermediate representation that JS and TypeScript are transformed to for
use by the rest of sphinx-js
This results from my former inability to review any but the most trivial
TypeScript PRs due to jsdoc's JSON output format being undocumented, often
surprising, and occasionally changing.
This IR is not intended to be a lossless representation of either jsdoc's or
typedoc's output. Nor is it meant to generalize to other uses like static
analysis. Ideally, it provides the minimum information necessary to render our
Sphinx templates about JS and TS entities. Any expansion or generalization of
the IR should be driven by needs of those templates and the (minimal) logic
around them. The complexity of doing otherwise has no payback.
I was conflicted about introducing an additional representation, since a
multiplicity of representations incurs conversion complexity costs at a
superlinear rate. However, I think it is essential complexity here. The
potentially simpler approach would have been to let the RST template vars
required by our handful of directives be the IR. However, we still would have
wanted to factor out formatting like the joining of types with "|" and the
unwrapping of comments, making another representation inevitable. Therefore,
let's at least have a well-documented one and one slightly more likely to
survive template changes.
This has to match js/ir.ts
"""
from collections.abc import Callable, Sequence
from typing import Any, Literal, ParamSpec, TypeVar
import cattrs
from attrs import Factory, define, field
from .analyzer_utils import dotted_path
@define
class TypeXRefIntrinsic:
name: str
type: Literal["intrinsic"] = "intrinsic"
@define
class TypeXRefInternal:
name: str
path: list[str]
type: Literal["internal"] = "internal"
kind: str | None = None
@define
class TypeXRefExternal:
name: str
package: str
# TODO: use snake case for these like for everything else
sourcefilename: str | None
qualifiedName: str | None
type: Literal["external"] = "external"
TypeXRef = TypeXRefExternal | TypeXRefInternal | TypeXRefIntrinsic
@define
class DescriptionName:
text: str
type: Literal["name"] = "name"
@define
class DescriptionText:
text: str
type: Literal["text"] = "text"
@define
class DescriptionCode:
code: str
type: Literal["code"] = "code"
DescriptionItem = DescriptionName | DescriptionText | DescriptionCode
Description = str | Sequence[DescriptionItem]
#: Human-readable type of a value. None if we don't know the type.
Type = str | list[str | TypeXRef] | None
class Pathname:
"""A partial or full path to a language entity.
Example: ``['./', 'dir/', 'dir/', 'file.', 'object.', 'object#', 'object']``
"""
def __init__(self, segments: Sequence[str]):
self.segments = segments
def __str__(self) -> str:
return "".join(self.segments)
def __repr__(self) -> str:
return "Pathname(%r)" % self.segments
def __eq__(self, other: Any) -> bool:
return isinstance(other, self.__class__) and self.segments == other.segments
def dotted(self) -> str:
return dotted_path(self.segments)
@define
class _NoDefault:
"""A conspicuous no-default value that will show up in templates to help
troubleshoot code paths that grab ``Param.default`` without checking
``Param.has_default`` first."""
_no_default: bool = True
def __repr__(self) -> str:
return "NO_DEFAULT"
NO_DEFAULT = _NoDefault()
@define(slots=False)
class _Member:
"""An IR object that is a member of another, as a method is a member of a
class or interface"""
#: Whether this member is required to be provided by a subclass of a class
#: or implementor of an interface
is_abstract: bool
#: Whether this member is optional in the TypeScript sense of being allowed
#: on but not required of an object to conform to a type
is_optional: bool
#: Whether this member can be accessed on the container itself rather than
#: just on instances of it
is_static: bool
#: Is a private member of a class or, at least in JSDoc, a @namespace:
is_private: bool
@define
class TypeParam:
name: str
extends: Type
description: Description = ""
@define
class Param:
"""A parameter of either a function or (in the case of TS, which has
classes parametrized by type) a class."""
name: str
#: The description text (like all other description fields in the IR)
#: retains any line breaks and subsequent indentation whitespace that were
#: in the source code.
description: Description = ""
has_default: bool = False
is_variadic: bool = False
type: Type | None = None
#: Return the default value of this parameter, string-formatted so it can
#: be immediately suffixed to an equal sign in a formal param list. For
#: example, the number 6 becomes the string "6" to create ``foo=6``. If
# : has_default=True, this must be set.
default: str | _NoDefault = NO_DEFAULT
def __attrs_post_init__(self) -> None:
if self.has_default and self.default is NO_DEFAULT:
raise ValueError(
"Tried to construct a Param with has_default=True but without `default` specified."
)
@define
class Exc:
"""One kind of exception that can be raised by a function"""
#: The type of exception can have
type: Type
description: Description
@define
class Return:
"""One kind of thing a function can return"""
#: The type this kind of return value can have
type: Type
description: Description
@define
class Module:
filename: str
deppath: str | None
path: Pathname
line: int
attributes: list["TopLevel"] = Factory(list)
functions: list["Function"] = Factory(list)
classes: list["Class"] = Factory(list)
interfaces: list["Interface"] = Factory(list)
type_aliases: list["TypeAlias"] = Factory(list)
@define(slots=False)
class TopLevel:
"""A language object with an independent existence
A TopLevel entity is a potentially strong entity in the database sense; one
of these can exist on its own and not merely as a datum attached to another
entity. For example, Returns do not qualify, since they cannot exist
without a parent Function. And, though a given Attribute may be attached to
a Class, Attributes can also exist top-level in a module.
These are also complex entities: the sorts of thing with the potential to
include the kinds of subentities referenced by the fields defined herein.
"""
#: The short name of the object, regardless of whether it's a class or
#: function or typedef or param.
#:
#: This is usually the same as the last item of path.segments but not
#: always. For example, in JSDoc Attributes defined with @property, name is
#: defined but path is empty. This was a shortcut and could be corrected at
#: some point. If it is, we can stop storing name as a separate field. Also
#: TypeScript constructors are named "new WhateverClass". They should
#: instead be called "constructor".
name: str
#: The namepath-like unambiguous identifier of the object, e.g. ``['./',
#: 'dir/', 'dir/', 'file/', 'object.', 'object#', 'object']``
path: Pathname
#: The basename of the file the object is from, e.g. "foo.js"
filename: str
#: The path to the dependency, i.e., the file the object is from.
#: Either absolute or relative to the root_for_relative_js_paths.
deppath: str | None
#: The human-readable description of the entity or '' if absent
description: Description
modifier_tags: list[str] = field(kw_only=True, factory=list)
block_tags: dict[str, Sequence[Description]] = field(kw_only=True, factory=dict)
#: Line number where the object (excluding any prefixing comment) begins
line: int | None
#: Explanation of the deprecation (which implies True) or True or False
deprecated: Description | bool
#: List of preformatted textual examples
examples: Sequence[Description]
#: List of paths to also refer the reader to
see_alsos: list[str]
#: Explicitly documented sub-properties of the object, a la jsdoc's
#: @properties
properties: list["Attribute"]
#: None if not exported for use by outside code. Otherwise, the Sphinx
#: dotted path to the module it is exported from, e.g. 'foo.bar'
exported_from: Pathname | None
#: Descriminator
kind: str = field(kw_only=True)
#: Is it a root documentation item? Used by autosummary.
documentation_root: bool = field(kw_only=True, default=False)
@define(slots=False)
class Attribute(TopLevel, _Member):
"""A property of an object
These are called attributes to match up with Sphinx's autoattribute
directive which is used to display them.
"""
#: The type this property's value can have
type: Type
readonly: bool = False
kind: Literal["attribute"] = "attribute"
@define
class Function(TopLevel, _Member):
"""A function or a method of a class"""
is_async: bool
params: list[Param]
exceptions: list[Exc]
returns: list[Return]
type_params: list[TypeParam] = Factory(list)
kind: Literal["function"] = "function"
@define
class _MembersAndSupers:
"""An IR object that can contain members and extend other types"""
#: Class members, concretized ahead of time for simplicity. (Otherwise,
#: we'd have to pass the doclets_by_class map in and keep it around, along
#: with a callable that would create the member IRs from it on demand.)
#: Does not include the default constructor.
members: list[Function | Attribute]
#: Objects this one extends: for example, superclasses of a class or
#: superinterfaces of an interface
supers: list[Type]
@define
class Interface(TopLevel, _MembersAndSupers):
"""An interface, a la TypeScript"""
type_params: list[TypeParam] = Factory(list)
kind: Literal["interface"] = "interface"
@define
class Class(TopLevel, _MembersAndSupers):
#: The default constructor for this class. Absent if the constructor is
#: inherited.
constructor_: Function | None
#: Whether this is an abstract class
is_abstract: bool
#: Interfaces this class implements
interfaces: list[Type]
# There's room here for additional fields like @example on the class doclet
# itself. These are supported and extracted by jsdoc, but they end up in an
# `undocumented: True` doclet and so are presently filtered out. But we do
# have the space to include them someday.
type_params: list[TypeParam] = Factory(list)
kind: Literal["class"] = "class"
@define
class TypeAlias(TopLevel):
type: Type
type_params: list[TypeParam] = Factory(list)
kind: Literal["typeAlias"] = "typeAlias"
TopLevelUnion = Class | Interface | Function | Attribute | TypeAlias
# Now make a serializer/deserializer.
# TODO: Add tests to make sure that serialization and deserialization are a
# round trip.
def json_to_ir(json: Any) -> list[TopLevelUnion]:
"""Structure raw json into a list of TopLevels"""
return converter.structure(json, list[TopLevelUnion])
converter = cattrs.Converter()
# We just serialize Pathname as a list
converter.register_unstructure_hook(Pathname, lambda x: x.segments)
converter.register_structure_hook(Pathname, lambda x, _: Pathname(x))
# Nothing else needs custom serialization. Add a decorator to register custom
# deserializers for the various unions.
P = ParamSpec("P")
T = TypeVar("T")
def _structure(*types: Any) -> Callable[[Callable[P, T]], Callable[P, T]]:
def dec(func: Callable[P, T]) -> Callable[P, T]:
for ty in types:
converter.register_structure_hook(ty, func)
return func
return dec
@_structure(Description, Description | bool)
def structure_description(x: Any, _: Any) -> Description | bool:
if isinstance(x, str):
return x
if isinstance(x, bool):
return x
return converter.structure(x, list[DescriptionItem])
def get_type_literal(t: type[DescriptionText]) -> str:
"""Take the "blah" from the type annotation in
type: Literal["blah"]
"""
return t.__annotations__["type"].__args__[0] # type:ignore[no-any-return]
description_type_map = {
get_type_literal(t): t for t in [DescriptionName, DescriptionText, DescriptionCode]
}
@_structure(DescriptionItem)
def structure_description_item(x: Any, _: Any) -> DescriptionItem:
# Look up the expected type of x from the value of x["type"]
return converter.structure(x, description_type_map[x["type"]])
@_structure(Type)
def structure_type(x: Any, _: Any) -> Type:
if isinstance(x, str) or x is None:
return x
return converter.structure(x, list[str | TypeXRef])
@_structure(str | TypeXRef)
def structure_str_or_xref(x: Any, _: Any) -> Type:
if isinstance(x, str):
return x
return converter.structure(x, TypeXRef) # type:ignore[arg-type]
@_structure(str | _NoDefault)
def structure_str_or_nodefault(x: Any, _: Any) -> str | _NoDefault:
if isinstance(x, str):
return x
return NO_DEFAULT
================================================
FILE: sphinx_js/js/cli.ts
================================================
import {
Application,
ArgumentsReader,
TypeDocReader,
PackageJsonReader,
TSConfigReader,
ProjectReflection,
} from "typedoc";
import { Converter } from "./convertTopLevel.ts";
import { SphinxJsConfig } from "./sphinxJsConfig.ts";
import { fileURLToPath } from "url";
import { redirectPrivateTypes } from "./redirectPrivateAliases.ts";
import { TopLevelIR } from "./ir.ts";
const ExitCodes = {
Ok: 0,
OptionError: 1,
CompileError: 3,
ValidationError: 4,
OutputError: 5,
ExceptionThrown: 6,
Watching: 7,
};
export class ExitError extends Error {
code: number;
constructor(code: number) {
super();
this.code = code;
}
}
async function bootstrapAppTypedoc0_25(args: string[]): Promise<Application> {
return await Application.bootstrapWithPlugins(
{
plugin: [fileURLToPath(import.meta.resolve("./typedocPlugin.ts"))],
},
[
new ArgumentsReader(0, args),
new TypeDocReader(),
new PackageJsonReader(),
new TSConfigReader(),
new ArgumentsReader(300, args),
],
);
}
async function makeApp(args: string[]): Promise<Application> {
// Most of this stuff is copied from typedoc/src/lib/cli.ts
let app = await bootstrapAppTypedoc0_25(args);
if (app.options.getValue("version")) {
console.log(app.toString());
throw new ExitError(ExitCodes.Ok);
}
app.extraData = {};
app.options.getValue("modifierTags").push("@hidetype", "@omitFromAutoModule");
app.options.getValue("blockTags").push("@destructure", "@summaryLink");
return app;
}
async function loadConfig(
configPath: string | undefined,
): Promise<SphinxJsConfig> {
if (!configPath) {
return {};
}
const configModule = await import(configPath);
return configModule.config;
}
async function typedocConvert(app: Application): Promise<ProjectReflection> {
// Most of this stuff is copied from typedoc/src/lib/cli.ts
const project = await app.convert();
if (!project) {
throw new ExitError(ExitCodes.CompileError);
}
const preValidationWarnCount = app.logger.warningCount;
app.validate(project);
const hadValidationWarnings =
app.logger.warningCount !== preValidationWarnCount;
if (app.logger.hasErrors()) {
throw new ExitError(ExitCodes.ValidationError);
}
if (
hadValidationWarnings &&
(app.options.getValue("treatWarningsAsErrors") ||
app.options.getValue("treatValidationWarningsAsErrors"))
) {
throw new ExitError(ExitCodes.ValidationError);
}
return project;
}
export async function run(
args: string[],
): Promise<[Application, TopLevelIR[]]> {
let app = await makeApp(args);
const userConfigPath = app.options.getValue("sphinxJsConfig");
const config = await loadConfig(userConfigPath);
app.logger.info(`Loaded user config from ${userConfigPath}`);
const symbolToType = redirectPrivateTypes(app);
await config.preConvert?.(app);
const project = await typedocConvert(app);
const basePath = app.options.getValue("basePath");
const converter = new Converter(project, basePath, config, symbolToType);
converter.computePaths();
const result = converter.convertAll();
await config.postConvert?.(app, project, converter.typedocToIRMap);
return [app, result];
}
================================================
FILE: sphinx_js/js/convertTopLevel.ts
================================================
import {
Comment,
CommentDisplayPart,
DeclarationReflection,
ParameterReflection,
ProjectReflection,
ReferenceType,
ReflectionKind,
ReflectionVisitor,
SignatureReflection,
SomeType,
TypeContext,
TypeParameterReflection,
} from "typedoc";
import {
referenceToXRef,
convertType,
convertTypeLiteral,
} from "./convertType.ts";
import {
NO_DEFAULT,
Attribute,
Class,
Description,
DescriptionItem,
Interface,
IRFunction,
Member,
Param,
Pathname,
Return,
TopLevelIR,
TopLevel,
Type,
TypeParam,
} from "./ir.ts";
import { sep, relative } from "path";
import { SphinxJsConfig } from "./sphinxJsConfig.ts";
import { ReadonlySymbolToType } from "./redirectPrivateAliases.ts";
export function parseFilePath(path: string, base_dir: string): string[] {
// First we want to know if path is under base_dir.
// Get directions from base_dir to the path
const rel = relative(base_dir, path);
let pathSegments: string[];
if (!rel.startsWith("..")) {
// We don't have to go up so path is under base_dir
pathSegments = rel.split(sep);
} else {
// It's not under base_dir... maybe it's in a global node_modules or
// something? This makes it look the same as if it were under a local
// node_modules.
pathSegments = path.split(sep);
pathSegments.reverse();
const idx = pathSegments.indexOf("node_modules");
if (idx !== -1) {
pathSegments = pathSegments.slice(0, idx + 1);
}
pathSegments.reverse();
}
// Remove the file suffix from the last entry if it exists. If there is no .,
// then this will leave it alone.
let lastEntry = pathSegments.pop();
if (lastEntry !== undefined) {
pathSegments.push(lastEntry.slice(0, lastEntry.lastIndexOf(".")));
}
// Add a . to the start and a / after every entry so that if we join the
// entries it looks like the correct relative path.
// Hopefully it was actually a relative path of some sort...
pathSegments.unshift(".");
for (let i = 0; i < pathSegments.length - 1; i++) {
pathSegments[i] += "/";
}
return pathSegments;
}
/**
* We currently replace {a : () => void} with {a() => void}. "a" is a reflection
* with a TypeLiteral type kind, and the type has name "__type". We don't want
* this to appear in the docs so we have to check for it and remove it.
*/
function isAnonymousTypeLiteral(
refl: DeclarationReflection | SignatureReflection,
): boolean {
return refl.kindOf(ReflectionKind.TypeLiteral) && refl.name === "__type";
}
/**
* A ReflectionVisitor that computes the path for each reflection for us.
*
* We want to compute the paths for both DeclarationReflections and
* SignatureReflections.
*/
class PathComputer implements ReflectionVisitor {
readonly basePath: string;
// The maps we're trying to fill in.
readonly pathMap: Map<DeclarationReflection | SignatureReflection, Pathname>;
readonly filePathMap: Map<
DeclarationReflection | SignatureReflection,
Pathname
>;
// Record which reflections are documentation roots. Used in sphinx for
// automodule and autosummary directives.
readonly documentationRoots: Set<DeclarationReflection | SignatureReflection>;
// State for the visitor
parentKind: ReflectionKind | undefined;
parentSegments: string[];
filePath: string[];
constructor(
basePath: string,
pathMap: Map<DeclarationReflection | SignatureReflection, Pathname>,
filePathMap: Map<DeclarationReflection | SignatureReflection, Pathname>,
documentationRoots: Set<DeclarationReflection | SignatureReflection>,
) {
this.pathMap = pathMap;
this.filePathMap = filePathMap;
this.basePath = basePath;
this.documentationRoots = documentationRoots;
this.parentKind = undefined;
this.parentSegments = [];
this.filePath = [];
}
/**
* If the name of the reflection is supposed to be a symbol, it should look
* something like [Symbol.iterator] but typedoc just shows it as [iterator].
* Downstream lexers to color the docs split on dots, but we don't want that
* because here the dot is part of the name. Instead, we add a dot lookalike.
*/
static fixSymbolName(refl: DeclarationReflection | SignatureReflection) {
const SYMBOL_PREFIX = "[Symbol\u2024";
if (refl.name.startsWith("[") && !refl.name.startsWith(SYMBOL_PREFIX)) {
// Probably a symbol (are there other reasons the name would start with "["?)
// \u2024 looks like a period but is not a period.
// This isn't ideal, but otherwise the coloring is weird.
refl.name = SYMBOL_PREFIX + refl.name.slice(1);
}
}
/**
* The main logic for this visitor. static for easier readability.
*/
static computePath(
refl: DeclarationReflection | SignatureReflection,
parentKind: ReflectionKind,
parentSegments: string[],
filePath: string[],
): Pathname {
// If no parentSegments, this is a "root", use the file path as the
// parentSegments.
// We have to copy the segments because we're going to mutate it.
const segments = Array.from(
parentSegments.length > 0 ? parentSegments : filePath,
);
// Skip some redundant names
const suppressReflName =
refl.kindOf(
// Module names are redundant with the file path
ReflectionKind.Module |
// Signature names are redundant with the callable. TODO: do we want to
// handle callables with multiple signatures?
ReflectionKind.ConstructorSignature |
ReflectionKind.CallSignature,
) || isAnonymousTypeLiteral(refl);
if (suppressReflName) {
return segments;
}
if (segments.length > 0) {
// Add delimiter. For most things use a . e.g., parent.name but for
// nonstatic class members we write Class#member
const delimiter =
parentKind === ReflectionKind.Class && !refl.flags.isStatic ? "#" : ".";
segments[segments.length - 1] += delimiter;
}
// Add the name of the current reflection to the list
segments.push(refl.name);
return segments;
}
setPath(refl: DeclarationReflection | SignatureReflection): Pathname {
PathComputer.fixSymbolName(refl);
const segments = PathComputer.computePath(
refl,
this.parentKind!,
this.parentSegments,
this.filePath,
);
if (isAnonymousTypeLiteral(refl)) {
// Rename the anonymous type literal to share its name with the attribute
// it is the type of.
refl.name = this.parentSegments.at(-1)!;
}
this.pathMap.set(refl, segments);
this.filePathMap.set(refl, this.filePath);
return segments;
}
// The visitor methods
project(project: ProjectReflection) {
// Compute the set of documentation roots.
// This consists of all children of the Project and all children of Modules.
for (const child of project.children || []) {
this.documentationRoots.add(child);
}
for (const module of project.getChildrenByKind(ReflectionKind.Module)) {
for (const child of module.children || []) {
this.documentationRoots.add(child);
}
}
// Visit children
project.children?.forEach((x) => x.visit(this));
}
declaration(refl: DeclarationReflection) {
if (refl.sources) {
this.filePath = parseFilePath(refl.sources![0].fileName, this.basePath);
}
const segments = this.setPath(refl);
// Update state for children
const origParentSegs = this.parentSegments;
const origParentKind = this.parentKind;
this.parentSegments = segments;
this.parentKind = refl.kind;
// Visit children
refl.children?.forEach((child) => child.visit(this));
refl.signatures?.forEach((child) => child.visit(this));
if (
refl.kind === ReflectionKind.Property &&
refl.type?.type == "reflection"
) {
// If the property has a function type, we replace it with a function
// described by the declaration. Just in case that happens we compute the
// path for the declaration here.
refl.type.declaration.visit(this);
}
// Restore state
this.parentSegments = origParentSegs;
this.parentKind = origParentKind;
}
signature(refl: SignatureReflection) {
this.setPath(refl);
}
}
// Some utilities for manipulating comments
/**
* Convert CommentDisplayParts from typedoc IR to sphinx-js comment IR.
* @param content List of CommentDisplayPart
* @returns
*/
function renderCommentContent(content: CommentDisplayPart[]): Description {
return content.map((x): DescriptionItem => {
if (x.kind === "code") {
return { type: "code", code: x.text };
}
if (x.kind === "text") {
return { type: "text", text: x.text };
}
throw new Error("Not implemented");
});
}
function renderCommentSummary(c: Comment | undefined): Description {
if (!c) {
return [];
}
return renderCommentContent(c.summary);
}
/**
* Compute a map from blockTagName to list of comment descriptions.
*/
function getCommentBlockTags(c: Comment | undefined): {
[key: string]: Description[];
} {
if (!c) {
return {};
}
const result: { [key: string]: Description[] } = {};
for (const tag of c.blockTags) {
const tagType = tag.tag.slice(1);
if (!(tagType in result)) {
result[tagType] = [];
}
const content: Description = [];
if (tag.name) {
// If the tag has a name field, add it as a DescriptionName
content.push({
type: "name",
text: tag.name,
});
}
content.push(...renderCommentContent(tag.content));
result[tagType].push(content);
}
return result;
}
/**
* The type returned by most methods on the converter.
*
* A pair, an optional TopLevel and an optional list of additional reflections
* to convert.
*/
type ConvertResult = [
TopLevelIR | undefined,
DeclarationReflection[] | undefined,
];
/**
* We generate some "synthetic parameters" when destructuring parameters. It
* would be possible to convert directly to our IR but it causes some code
* duplication. Instead, we keep track of the subset of fields that `paramToIR`
* actually needs here.
*/
type ParamReflSubset = Pick<
ParameterReflection,
"comment" | "defaultValue" | "flags" | "name" | "type"
>;
/**
* Main class for creating IR from the ProjectReflection.
*
* The main toIr logic is a sort of visitor for ReflectionKinds. We don't use
* ReflectionVisitor because the division it uses for visitor methods is too
* coarse.
*
* We visit in a breadth-first order, not for any super compelling reason.
*/
export class Converter {
readonly project: ProjectReflection;
readonly basePath: string;
readonly config: SphinxJsConfig;
readonly symbolToType: ReadonlySymbolToType;
readonly pathMap: Map<DeclarationReflection | SignatureReflection, Pathname>;
readonly filePathMap: Map<
DeclarationReflection | SignatureReflection,
Pathname
>;
readonly documentationRoots: Set<DeclarationReflection | SignatureReflection>;
readonly typedocToIRMap: Map<DeclarationReflection, TopLevel>;
constructor(
project: ProjectReflection,
basePath: string,
config: SphinxJsConfig,
symbolToType: ReadonlySymbolToType,
) {
this.project = project;
this.basePath = basePath;
this.config = config;
this.symbolToType = symbolToType;
this.pathMap = new Map();
this.filePathMap = new Map();
this.documentationRoots = new Set();
this.typedocToIRMap = new Map();
}
convertType(type: SomeType, context: TypeContext = TypeContext.none): Type {
return convertType(
this.basePath,
this.pathMap,
this.symbolToType,
type,
context,
);
}
referenceToXRef(type: ReferenceType): Type {
return referenceToXRef(
this.basePath,
this.pathMap,
this.symbolToType,
type,
);
}
computePaths() {
this.project.visit(
new PathComputer(
this.basePath,
this.pathMap,
this.filePathMap,
this.documentationRoots,
),
);
}
/**
* Convert all Reflections.
*/
convertAll(): TopLevelIR[] {
const todo = Array.from(this.project.children!);
const result: TopLevelIR[] = [];
while (todo.length) {
const node = todo.pop()!;
const [converted, rest] = this.toIr(node);
if (converted) {
this.typedocToIRMap.set(node, converted);
result.push(converted);
}
todo.push(...(rest || []));
}
return result;
}
/**
* Convert the reflection and return a pair, the conversion result and a list
* of descendent Reflections to convert. These descendents are either children
* or signatures.
*
* @param object The reflection to convert
* @returns A pair, a possible result IR object, and a list of descendent
* Reflections that still need converting.
*/
toIr(object: DeclarationReflection | SignatureReflection): ConvertResult {
// ReflectionKinds that we give no conversion.
if (
object.kindOf(
ReflectionKind.Module |
ReflectionKind.Namespace |
// TODO: document enums
ReflectionKind.Enum |
ReflectionKind.EnumMember |
// A ReferenceReflection is when we reexport something.
// TODO: should we handle this somehow?
ReflectionKind.Reference,
)
) {
// TODO: The children of these have no rendered parent in the docs. If
// "object" is marked as a documentation_root, maybe the children should
// be too?
return [undefined, (object as DeclarationReflection).children];
}
const kind = ReflectionKind[object.kind];
const convertFunc = `convert${kind}` as keyof this;
if (!this[convertFunc]) {
throw new Error(`No known converter for kind ${kind}`);
}
// @ts-ignore
const result: ConvertResult = this[convertFunc](object);
if (this.documentationRoots.has(object) && result[0]) {
result[0].documentation_root = true;
}
return result;
}
// Reflection visitor methods
convertFunction(func: DeclarationReflection): ConvertResult {
return [this.functionToIR(func), func.children];
}
convertMethod(func: DeclarationReflection): ConvertResult {
return [this.functionToIR(func), func.children];
}
convertConstructor(func: DeclarationReflection): ConvertResult {
return [this.functionToIR(func), func.children];
}
convertVariable(v: DeclarationReflection): ConvertResult {
if (!v.type) {
throw new Error(`Type of ${v.name} is undefined`);
}
let type: Type;
if (v.comment?.modifierTags.has("@hidetype")) {
type = [];
} else {
type = this.convertType(v.type);
}
const result: Attribute = {
...this.memberProps(v),
...this.topLevelProperties(v),
readonly: false,
kind: "attribute",
type,
};
return [result, v.children];
}
/**
* Return the unambiguous pathnames of implemented interfaces or extended
* classes.
*/
relatedTypes(
cls: DeclarationReflection,
kind: "extendedTypes" | "implementedTypes",
): Type[] {
const origTypes = cls[kind] || [];
const result: Type[] = [];
for (const t of origTypes) {
if (t.type !== "reference") {
continue;
}
result.push(this.referenceToXRef(t));
}
return result;
}
convertClass(cls: DeclarationReflection): ConvertResult {
const [constructor_, members] = this.constructorAndMembers(cls);
const result: Class = {
constructor_,
members,
supers: this.relatedTypes(cls, "extendedTypes"),
is_abstract: cls.flags.isAbstract,
interfaces: this.relatedTypes(cls, "implementedTypes"),
type_params: this.typeParamsToIR(cls.typeParameters),
...this.topLevelProperties(cls),
kind: "class",
};
return [result, cls.children];
}
convertInterface(cls: DeclarationReflection): ConvertResult {
const [_, members] = this.constructorAndMembers(cls);
const result: Interface = {
members,
supers: this.relatedTypes(cls, "extendedTypes"),
type_params: this.typeParamsToIR(cls.typeParameters),
...this.topLevelProperties(cls),
kind: "interface",
};
return [result, cls.children];
}
convertProperty(prop: DeclarationReflection): ConvertResult {
if (
prop.type?.type === "reflection" &&
prop.type.declaration.kindOf(ReflectionKind.TypeLiteral) &&
prop.type.declaration.signatures?.length
) {
// Render {f: () => void} like {f(): void}
// TODO: unclear if this is the right behavior. Maybe there should be a
// way to pick?
const functionIR = this.functionToIR(prop.type.declaration);
// Preserve the property's own documentation if it exists
functionIR.description = renderCommentSummary(prop.comment);
// Preserve the optional flag from the original property
functionIR.is_optional = prop.flags.isOptional;
return [functionIR, []];
}
let type: Type;
if (prop.comment?.modifierTags.has("@hidetype")) {
// We should probably also be able to hide the type of a thing with a
// function type literal type...
type = [];
} else {
type = this.convertType(prop.type!);
}
const result: Attribute = {
type,
...this.memberProps(prop),
...this.topLevelProperties(prop),
description: renderCommentSummary(prop.comment),
readonly: prop.flags.isReadonly,
kind: "attribute",
};
return [result, prop.children];
}
/**
* An Accessor is a thing with a getter or a setter. It should look exactly
* like a Property in the rendered docs since the distinction is an
* implementation detail.
*
* Specifically:
* 1. an Accessor with a getter but no setter should be rendered as a readonly
* Property.
* 2. an Accessor with a getter and a setter should be rendered as a
* read/write Property
* 3. Not really sure what to do with an Accessor with a setter and no getter.
* That's kind of weird.
*/
convertAccessor(prop: DeclarationReflection): ConvertResult {
let type: SomeType;
let sig: SignatureReflection;
if (prop.getSignature) {
// There's no signature to speak of for a getter: only a return type.
sig = prop.getSignature;
type = sig.type!;
} else {
if (!prop.setSignature) {
throw new Error("???");
}
// ES6 says setters have exactly 1 param.
sig = prop.setSignature;
type = sig.parameters![0].type!;
}
// If there's no setter say it's readonly
const readonly = !prop.setSignature;
const result: Attribute = {
type: this.convertType(type),
readonly,
...this.memberProps(prop),
...this.topLevelProperties(prop),
kind: "attribute",
};
result.description = renderCommentSummary(sig.comment);
return [result, prop.children];
}
convertClassChild(child: DeclarationReflection): IRFunction | Attribute {
if (
!child.kindOf(
ReflectionKind.Accessor |
ReflectionKind.Constructor |
ReflectionKind.Method |
ReflectionKind.Property,
)
) {
throw new TypeError(
"Expected an Accessor, Constructor, Method, or Property",
);
}
// Should we assert that the "descendants" component is empty?
return this.toIr(child)[0] as IRFunction | Attribute;
}
/**
* Return the IR for the constructor and other members of a class or
* interface.
*
* In TS, a constructor may have multiple (overloaded) type signatures but
* only one implementation. (Same with functions.) So there's at most 1
* constructor to return. Return None for the constructor if it is inherited
* or implied rather than explicitly present in the class.
*
* @param refl Class or Interface
* @returns A tuple of (constructor Function, list of other members)
*/
constructorAndMembers(
refl: DeclarationReflection,
): [IRFunction | null, (IRFunction | Attribute)[]] {
let constructor: IRFunction | null = null;
const members: (IRFunction | Attribute)[] = [];
for (const child of refl.children || []) {
if (child.inheritedFrom) {
continue;
}
if (child.kindOf(ReflectionKind.Constructor)) {
// This really, really should happen exactly once per class.
constructor = this.functionToIR(child);
constructor.returns = [];
continue;
}
members.push(this.convertClassChild(child));
}
return [constructor, members];
}
/**
* Compute common properties for all class members.
*/
memberProps(refl: DeclarationReflection): Member {
return {
is_abstract: refl.flags.isAbstract,
is_optional: refl.flags.isOptional,
is_static: refl.flags.isStatic,
is_private: refl.flags.isPrivate,
};
}
/**
* Compute common properties for all TopLevels.
*/
topLevelProperties(
refl: DeclarationReflection | SignatureReflection,
): TopLevel {
const path = this.pathMap.get(refl);
const filePath = this.filePathMap.get(refl)!;
if (!path) {
throw new Error(`Missing path for ${refl.name}`);
}
const block_tags = getCommentBlockTags(refl.comment);
let deprecated: Description | boolean =
block_tags["deprecated"]?.[0] || false;
if (deprecated && deprecated.length === 0) {
deprecated = true;
}
return {
name: refl.name,
path,
deppath: filePath.join(""),
filename: "",
description: renderCommentSummary(refl.comment),
modifier_tags: Array.from(refl.comment?.modifierTags || []),
block_tags,
deprecated,
examples: block_tags["example"] || [],
properties: [],
see_alsos: [],
exported_from: filePath,
line: refl.sources?.[0].line || null,
documentation_root: false,
};
}
/**
* We want to document a destructured argument as if it were several separate
* arguments. This finds complex inline object types in the arguments list of
* a function and "destructures" them into separately documented arguments.
*
* E.g., a function
*
* /**
* * @param options
* * @destructure options
* *./
* function f({x , y } : {
* /** The x value *./
* x : number,
* /** The y value *./
* y : string
* }){ ... }
*
* should be documented like:
*
* options.x (number) The x value
* options.y (number) The y value
*/
_destructureParam(param: ParameterReflection): ParamReflSubset[] {
const type = param.type;
if (type?.type !== "reflection") {
throw new Error("Unexpected");
}
const decl = type.declaration;
const children = decl.children!;
// Sort destructured parameter by order in the type declaration in the
// source file. Before we sort they are in alphabetical order by name. Maybe
// we should have a way to pick the desired behavior? There are three
// reasonable orders:
//
// 1. alphabetical by name
// 2. In order of the @options.b annotations
// 3. In order of their declarations in the type
//
// This does order 3
children.sort(
({ sources: a }, { sources: b }) =>
a![0].line - b![0].line || a![0].character - b![0].character,
);
const result: ParamReflSubset[] = [];
for (const child of children) {
result.push({
name: param.name + "." + child.name,
type: child.type,
comment: child.comment,
defaultValue: undefined,
flags: child.flags,
});
}
return result;
}
_destructureParams(sig: SignatureReflection): ParamReflSubset[] {
const result = [];
// Destructure a parameter if it's type is a reflection and it is requested
// with @destructure or _shouldDestructureArg.
const destructureTargets = sig.comment
?.getTags("@destructure")
.flatMap((tag) => tag.content[0].text.split(" "));
const shouldDestructure = (p: ParameterReflection) => {
if (p.type?.type !== "reflection") {
return false;
}
if (destructureTargets?.includes(p.name)) {
return true;
}
const shouldDestructure = this.config.shouldDestructureArg;
return shouldDestructure && shouldDestructure(p);
};
for (const p of sig.parameters || []) {
if (shouldDestructure(p)) {
result.push(...this._destructureParam(p));
} else {
result.push(p);
}
}
return result;
}
/**
* Convert a signature parameter
*/
paramToIR(param: ParamReflSubset): Param {
let type: Type = [];
if (param.type) {
type = this.convertType(param.type);
}
let description = renderCommentSummary(param.comment);
if (description.length === 0 && param.type?.type === "reflection") {
// If the parameter type is given as the typeof something else, use the
// description from the target?
// TODO: isn't this a weird thing to do here? I think we should remove it?
description = renderCommentSummary(
param.type.declaration?.signatures?.[0].comment,
);
}
return {
name: param.name,
has_default: !!param.defaultValue,
default: param.defaultValue || NO_DEFAULT,
is_variadic: param.flags.isRest,
description,
type,
};
}
/**
* Convert callables: Function, Method, and Constructor.
* @param func
* @returns
*/
functionToIR(func: DeclarationReflection): IRFunction {
// There's really nothing in the function itself; all the interesting bits
// are in the 'signatures' property. We support only the first signature at
// the moment, because to do otherwise would create multiple identical
// pathnames to the same function, which would cause the suffix tree to
// raise an exception while being built. An eventual solution might be to
// store the signatures in a one-to- many attr of Functions.
const first_sig = func.signatures![0]; // Should always have at least one
// Make sure name matches, can be different in case this comes from
// isAnonymousTypeLiteral returning true.
first_sig.name = func.name;
const params = this._destructureParams(first_sig);
let returns: Return[] = [];
let is_async = false;
// We want to suppress the return type for constructors (it's technically
// correct that it returns a class instance but it looks weird).
// Also hide explicit void return type.
const voidReturnType =
func.kindOf(ReflectionKind.Constructor) ||
!first_sig.type ||
(first_sig.type.type === "intrinsic" && first_sig.type.name === "void");
let type_params = this.typeParamsToIR(first_sig.typeParameters);
if (func.kindOf(ReflectionKind.Constructor)) {
// I think this is wrong
// TODO: remove it
type_params = this.typeParamsToIR(
(func.parent as DeclarationReflection).typeParameters,
);
}
const topLevel = this.topLevelProperties(first_sig);
if (!voidReturnType && first_sig.type) {
// Compute return comment and return annotation.
const returnType = this.convertType(first_sig.type);
const description = topLevel.block_tags.returns?.[0] || [];
returns = [{ type: returnType, description }];
// Put async in front of the function if it returns a Promise.
// Question: Is there any important difference between an actual async
// function and a non-async one that returns a Promise?
is_async =
first_sig.type.type === "reference" &&
first_sig.type.name === "Promise";
}
return {
...topLevel,
...this.memberProps(func),
is_async,
params: params?.map(this.paramToIR.bind(this)) || [],
type_params,
returns,
exceptions: [],
kind: "function",
};
}
typeParamsToIR(
typeParams: TypeParameterReflection[] | undefined,
): TypeParam[] {
return typeParams?.map((typeParam) => this.typeParamToIR(typeParam)) || [];
}
typeParamToIR(typeParam: TypeParameterReflection): TypeParam {
const extends_ = typeParam.type
? this.convertType(typeParam.type, TypeContext.referenceTypeArgument)
: null;
return {
name: typeParam.name,
extends: extends_,
description: renderCommentSummary(typeParam.comment),
};
}
convertTypeAlias(ty: DeclarationReflection): ConvertResult {
let type;
if (ty.type) {
type = this.convertType(ty.type);
} else {
// Handle this change:
// https://github.com/TypeStrong/typedoc/commit/ca94f7eaecf90c25d6377e20c405626817de1e26#diff-14759d25b74ca53aee4558d0e26c85eee3c13484ea3ccdf28872b906829ef6f8R380-R390
type = convertTypeLiteral(
this.basePath,
this.pathMap,
this.symbolToType,
ty,
);
}
const ir: TopLevelIR = {
...this.topLevelProperties(ty),
kind: "typeAlias",
type,
type_params: this.typeParamsToIR(ty.typeParameters),
};
return [ir, ty.children];
}
}
================================================
FILE: sphinx_js/js/convertType.ts
================================================
import {
ArrayType,
ConditionalType,
DeclarationReflection,
IndexedAccessType,
InferredType,
IntersectionType,
IntrinsicType,
LiteralType,
MappedType,
NamedTupleMember,
OptionalType,
PredicateType,
QueryType,
ReferenceType,
ReflectionKind,
ReflectionType,
RestType,
SignatureReflection,
SomeType,
TemplateLiteralType,
TupleType,
TypeContext,
TypeOperatorType,
TypeVisitor,
UnionType,
UnknownType,
} from "typedoc";
import {
Type,
TypeXRefExternal,
TypeXRefInternal,
intrinsicType,
} from "./ir.js";
import { parseFilePath } from "./convertTopLevel.js";
import { ReadonlySymbolToType } from "./redirectPrivateAliases.js";
/**
* Convert types into a list of strings and XRefs.
*
* Most visitor nodes should be similar to the implementation of getTypeString
* on the same type.
*/
class TypeConverter implements TypeVisitor<Type> {
private readonly basePath: string;
// For resolving XRefs.
private readonly reflToPath: ReadonlyMap<
DeclarationReflection | SignatureReflection,
string[]
>;
private readonly symbolToType: ReadonlySymbolToType;
constructor(
basePath: string,
reflToPath: ReadonlyMap<
DeclarationReflection | SignatureReflection,
string[]
>,
symbolToType: ReadonlySymbolToType,
) {
this.basePath = basePath;
this.reflToPath = reflToPath;
this.symbolToType = symbolToType;
}
/**
* Helper for inserting type parameters
*/
addTypeArguments(
type: { typeArguments?: SomeType[] | undefined },
l: Type,
): Type {
if (!type.typeArguments || type.typeArguments.length === 0) {
return l;
}
l.push("<");
for (const arg of type.typeArguments) {
l.push(...arg.visit(this));
l.push(", ");
}
l.pop();
l.push(">");
return l;
}
/**
* Convert the type, maybe add parentheses
*/
convert(type: SomeType, context: TypeContext): Type {
const result = type.visit(this);
if (type.needsParenthesis(context)) {
result.unshift("(");
result.push(")");
}
return result;
}
conditional(type: ConditionalType): Type {
return [
...this.convert(type.checkType, TypeContext.conditionalCheck),
" extends ",
...this.convert(type.extendsType, TypeContext.conditionalExtends),
" ? ",
...this.convert(type.trueType, TypeContext.conditionalTrue),
" : ",
...this.convert(type.falseType, TypeContext.conditionalFalse),
];
}
indexedAccess(type: IndexedAccessType): Type {
return [
...this.convert(type.objectType, TypeContext.indexedObject),
"[",
...this.convert(type.indexType, TypeContext.indexedIndex),
"]",
];
}
inferred(type: InferredType): Type {
if (type.constraint) {
return [
`infer ${type.name} extends `,
...this.convert(type.constraint, TypeContext.inferredConstraint),
];
}
return [`infer ${type.name}`];
}
intersection(type: IntersectionType): Type {
const result: Type = [];
for (const elt of type.types) {
result.push(...this.convert(elt, TypeContext.intersectionElement));
result.push(" & ");
}
result.pop();
return result;
}
intrinsic(type: IntrinsicType): Type {
return [intrinsicType(type.name)];
}
literal(type: LiteralType): Type {
if (type.value === null) {
return [intrinsicType("null")];
}
return [JSON.stringify(type.value)];
}
mapped(type: MappedType): Type {
const read = {
"+": "readonly ",
"-": "-readonly ",
"": "",
}[type.readonlyModifier ?? ""];
const opt = {
"+": "?",
"-": "-?",
"": "",
}[type.optionalModifier ?? ""];
const parts: Type = [
"{ ",
read,
"[",
type.parameter,
" in ",
...this.convert(type.parameterType, TypeContext.mappedParameter),
];
if (type.nameType) {
parts.push(
" as ",
...this.convert(type.nameType, TypeContext.mappedName),
);
}
parts.push(
"]",
opt,
": ",
...this.convert(type.templateType, TypeContext.mappedTemplate),
" }",
);
return parts;
}
optional(type: OptionalType): Type {
return [
...this.convert(type.elementType, TypeContext.optionalElement),
"?",
];
}
predicate(type: PredicateType): Type {
// Consider using typedoc's representation for this instead of this custom
// string.
return [
intrinsicType("boolean"),
" (typeguard for ",
...type.targetType!.visit(this),
")",
];
}
query(type: QueryType): Type {
return [
"typeof ",
...this.convert(type.queryType, TypeContext.queryTypeTarget),
];
}
/**
* If it's a reference to a private type alias, replace it with a reflection.
* Otherwise return undefined.
*/
convertPrivateReferenceToReflection(type: ReferenceType): Type | undefined {
if (type.reflection) {
const refl = type.reflection as DeclarationReflection;
// If it's private, we don't really want to emit an XRef to it. In the
// typedocPlugin.ts we tried to calculate Reflections for these, so now
// we try to look it up. I couldn't get the line+column numbers to match
// up so in this case we index on file name and reference name.
// Another place where we incorrectly handle merged declarations
const src = refl?.sources?.[0];
if (!src) {
return undefined;
}
const newTarget = this.symbolToType.get(
`${src.fullFileName}:${refl.name}`,
);
if (newTarget) {
// TODO: this doesn't handle parentheses correctly.
return newTarget.visit(this);
}
return undefined;
}
if (!type.symbolId) {
throw new Error("This should not happen");
}
// See if this refers to a private type. In that case we should inline the
// type reflection rather than referring to the non-exported name. Ideally
// we should key on position rather than name (the same file can have
// multiple private types with the same name potentially). But it doesn't
// seem to be working.
const newTarget = this.symbolToType.get(
`${type.symbolId.fileName}:${type.name}`,
);
if (newTarget) {
// TODO: this doesn't handle parentheses correctly.
return newTarget.visit(this);
}
return undefined;
}
/**
* Convert a reference type to either an XRefExternal or an XRefInternal. It
* works on things that `convertPrivateReferenceToReflection` but it will
* throw an error if the type `isIntentionallyBroken`.
*
* This logic is also used for relatedTypes for classes (extends and
* implements).
* TODO: handle type arguments in extends and implements.
*/
convertReferenceToXRef(type: ReferenceType): Type {
if (type.isIntentionallyBroken()) {
throw new Error("Bad type");
}
if (type.reflection) {
const path = this.reflToPath.get(
type.reflection as DeclarationReflection,
);
if (!path) {
throw new Error(
`Broken internal xref to ${type.reflection?.toStringHierarchy()}`,
);
}
const xref: TypeXRefInternal = {
name: type.name,
path,
type: "internal",
};
return this.addTypeArguments(type, [xref]);
}
if (!type.symbolId) {
throw new Error("This shouldn't happen");
}
const path = parseFilePath(type.symbolId?.fileName ?? "", this.basePath);
if (path.includes("node_modules/")) {
// External reference
const xref: TypeXRefExternal = {
name: type.name,
package: type.package!,
qualifiedName: type.symbolId.qualifiedName || null,
sourcefilename: type.symbolId.fileName || null,
type: "external",
};
return this.addTypeArguments(type, [xref]);
} else {
// TODO: I'm not sure that it's right to generate an internal xref here.
// We need better test coverage for this code path.
const xref: TypeXRefInternal = {
name: type.name,
path,
type: "internal",
};
return this.addTypeArguments(type, [xref]);
}
}
reference(type: ReferenceType): Type {
// if we got a reflection use that. It's not all that clear how to deal
// with type arguments here though...
const res = this.convertPrivateReferenceToReflection(type);
if (res) {
return res;
}
if (type.isIntentionallyBroken()) {
// If it's intentionally broken, don't add an xref. It's probably a type
// parameter.
return this.addTypeArguments(type, [type.name]);
} else {
return this.convertReferenceToXRef(type);
}
}
reflection(type: ReflectionType): Type {
if (type.declaration.kindOf(ReflectionKind.TypeLiteral)) {
return this.convertTypeLiteral(type.declaration);
}
if (type.declaration.kindOf(ReflectionKind.Constructor)) {
const result = this.convertSignature(type.declaration.signatures![0]);
result.unshift("{new ");
result.push("}");
return result;
}
if (type.declaration.kindOf(ReflectionKind.FunctionOrMethod)) {
return this.convertSignature(type.declaration.signatures![0]);
}
throw new Error("Not implemented");
}
rest(type: RestType): Type {
return ["...", ...this.convert(type.elementType, TypeContext.restElement)];
}
templateLiteral(type: TemplateLiteralType): Type {
return [
"`",
type.head,
...type.tail.flatMap(([type, text]) => {
return [
"${",
...this.convert(type, TypeContext.templateLiteralElement),
"}",
text,
];
}),
"`",
];
}
tuple(type: TupleType): Type {
const result: Type = [];
for (const elt of type.elements) {
result.push(...this.convert(elt, TypeContext.tupleElement));
result.push(", ");
}
result.pop();
result.unshift("[");
result.push("]");
return result;
}
namedTupleMember(type: NamedTupleMember): Type {
const result: Type = [`${type.name}${type.isOptional ? "?" : ""}: `];
result.push(...this.convert(type.element, TypeContext.tupleElement));
return result;
}
typeOperator(type: TypeOperatorType): Type {
return [
type.operator,
" ",
...this.convert(type.target, TypeContext.typeOperatorTarget),
];
}
union(type: UnionType): Type {
const result: Type = [];
for (const elt of type.types) {
result.push(...this.convert(elt, TypeContext.unionElement));
result.push(" | ");
}
result.pop();
return result;
}
unknown(type: UnknownType): Type {
// I'm not sure how we get here: generally nobody explicitly annotates
// unknown, maybe it's inferred sometimes?
return [type.name];
}
array(t: ArrayType): Type {
const res = this.convert(t.elementType, TypeContext.arrayElement);
res.push("[]");
return res;
}
convertSignature(sig: SignatureReflection): Type {
const result: Type = ["("];
for (const param of sig.parameters || []) {
result.push(param.name + ": ");
result.push(...(param.type?.visit(this) || []));
result.push(", ");
}
if (sig.parameters?.length) {
result.pop();
}
result.push(") => ");
if (sig.type) {
result.push(...sig.type.visit(this));
} else {
result.push(intrinsicType("void"));
}
return result;
}
convertTypeLiteral(lit: DeclarationReflection): Type {
if (lit.signatures) {
return this.convertSignature(lit.signatures[0]);
}
const result: Type = ["{ "];
// lit.indexSignature for 0.25.x, lit.indexSignatures for 0.26.0 and later.
// @ts-ignore
const index_sig = lit.indexSignature ?? lit.indexSignatures?.[0];
if (index_sig) {
if (index_sig.parameters?.length !== 1) {
throw new Error("oops");
}
const key = index_sig.parameters[0];
// There's no exact TypeContext for indexedAccess b/c typedoc doesn't
// render it like this. mappedParameter and mappedTemplate look quite
// similar:
// [k in mappedParam]: mappedTemplate
// vs
// [k: keyType]: valueType
const keyType = this.convert(key.type!, TypeContext.mappedParameter);
const valueType = this.convert(
index_sig.type!,
TypeContext.mappedTemplate,
);
result.push("[", key.name, ": ");
result.push(...keyType);
result.push("]", ": ");
result.push(...valueType);
result.push("; ");
}
for (const child of lit.children || []) {
result.push(child.name);
if (child.flags.isOptional) {
result.push("?: ");
} else {
result.push(": ");
}
result.push(...(child.type?.visit(this) || []));
result.push("; ");
}
result.push("}");
return result;
}
}
export function convertType(
basePath: string,
reflToPath: ReadonlyMap<
DeclarationReflection | SignatureReflection,
string[]
>,
symbolToType: ReadonlySymbolToType,
type: SomeType,
context: TypeContext = TypeContext.none,
): Type {
const typeConverter = new TypeConverter(basePath, reflToPath, symbolToType);
return typeConverter.convert(type, context);
}
export function convertTypeLiteral(
basePath: string,
reflToPath: ReadonlyMap<
DeclarationReflection | SignatureReflection,
string[]
>,
symbolToType: ReadonlySymbolToType,
type: DeclarationReflection,
): Type {
const typeConverter = new TypeConverter(basePath, reflToPath, symbolToType);
return typeConverter.convertTypeLiteral(type);
}
export function referenceToXRef(
basePath: string,
reflToPath: ReadonlyMap<
DeclarationReflection | SignatureReflection,
string[]
>,
symbolToType: ReadonlySymbolToType,
type: ReferenceType,
): Type {
const converter = new TypeConverter(basePath, reflToPath, symbolToType);
return converter.convertReferenceToXRef(type);
}
================================================
FILE: sphinx_js/js/importHooks.mjs
================================================
async function tryResolve(specifier, context, nextResolve) {
try {
return await nextResolve(specifier, context);
} catch (e) {
if (e.code !== "ERR_MODULE_NOT_FOUND") {
// Unusual error let it propagate
throw e;
}
}
}
// An import hook to pick up packages in the node_modules that typedoc is
// installed into
export async function resolve(specifier, context, nextResolve) {
// Take an `import` or `require` specifier and resolve it to a URL.
const origURL = context.parentURL;
const fallbackURL = `file:${process.env["TYPEDOC_NODE_MODULES"]}/`;
for (const parentURL of [origURL, fallbackURL]) {
context.parentURL = parentURL;
const res = await tryResolve(specifier, context, nextResolve);
context.parentURL = origURL;
if (res) {
return res;
}
}
// If we get here, this will throw an error.
return nextResolve(specifier, context);
}
================================================
FILE: sphinx_js/js/ir.ts
================================================
// Define the types for our IR. Must match the cattrs+json serialization
// format from ir.py
export type TypeXRefIntrinsic = {
name: string;
type: "intrinsic";
};
export function intrinsicType(name: string): TypeXRefIntrinsic {
return {
name,
type: "intrinsic",
};
}
export type TypeXRefInternal = {
name: string;
path: string[];
type: "internal";
};
export type TypeXRefExternal = {
name: string;
package: string;
sourcefilename: string | null;
qualifiedName: string | null;
type: "external";
};
export type TypeXRef = TypeXRefExternal | TypeXRefInternal | TypeXRefIntrinsic;
export type Type = (string | TypeXRef)[];
export type DescriptionName = {
text: string;
type: "name";
};
export type DescriptionText = {
text: string;
type: "text";
};
export type DescriptionCode = {
code: string;
type: "code";
};
export type DescriptionItem =
| DescriptionName
| DescriptionText
| DescriptionCode;
export type Description = DescriptionItem[];
export type Pathname = string[];
export type NoDefault = { _no_default: true };
export const NO_DEFAULT: NoDefault = { _no_default: true };
export type Member = {
is_abstract: boolean;
is_optional: boolean;
is_static: boolean;
is_private: boolean;
};
export type TypeParam = {
name: string;
extends: Type | null;
description: Description;
};
export type Param = {
name: string;
description: Description;
is_variadic: boolean;
has_default: boolean;
default: string | NoDefault;
type: Type;
};
export type Return = {
type: Type;
description: Description;
};
export type Module = {
filename: string;
deppath: string;
path: Pathname;
line: number;
attributes: TopLevel[];
functions: IRFunction[];
classes: Class[];
};
export type TopLevel = {
name: string;
path: Pathname;
filename: string;
deppath: string;
description: Description;
modifier_tags: string[];
block_tags: { [key: string]: Description[] };
line: number | null;
deprecated: Description | boolean;
examples: Description[];
see_alsos: string[];
properties: Attribute[];
exported_from: Pathname | null;
documentation_root: boolean;
};
export type Attribute = TopLevel &
Member & {
type: Type;
readonly: boolean;
kind: "attribute";
};
export type IRFunction = TopLevel &
Member & {
is_async: boolean;
params: Param[];
returns: Return[];
type_params: TypeParam[];
kind: "function";
exceptions: never[];
};
export type _MembersAndSupers = {
members: (IRFunction | Attribute)[];
supers: Type[];
};
export type Interface = TopLevel &
_MembersAndSupers & {
type_params: TypeParam[];
kind: "interface";
};
export type Class = TopLevel &
_MembersAndSupers & {
constructor_: IRFunction | null;
is_abstract: boolean;
interfaces: Type[];
type_params: TypeParam[];
kind: "class";
};
export type TypeAlias = TopLevel & {
kind: "typeAlias";
type: Type;
type_params: TypeParam[];
};
export type TopLevelIR = Attribute | IRFunction | Class | Interface | TypeAlias;
================================================
FILE: sphinx_js/js/main.ts
================================================
import { writeFile } from "fs/promises";
import { ExitError, run } from "./cli.ts";
async function main() {
const start = Date.now();
const args = process.argv.slice(2);
let app, result;
try {
[app, result] = await run(args);
} catch (e) {
if (e instanceof ExitError) {
return e.code;
}
throw e;
}
const space = app.options.getValue("pretty") ? "\t" : "";
const res = JSON.stringify([result, app.extraData], null, space);
const json = app.options.getValue("json");
await writeFile(json, res);
app.logger.info(`JSON written to ${json}`);
app.logger.verbose(`JSON rendering took ${Date.now() - start}ms`);
return 0;
}
process.exit(await main());
================================================
FILE: sphinx_js/js/package.json
================================================
{
"name": "sphinx_js",
"type": "module",
"dependencies": {
"tsx": "^4.9.0",
"typedoc": "^0.25.13"
},
"devDependencies": {
"@types/node": "^20.12.7"
}
}
================================================
FILE: sphinx_js/js/redirectPrivateAliases.ts
================================================
/**
* This is very heavily inspired by typedoc-plugin-missing-exports.
*
* The goal isn't to document the missing exports, but rather to remove them
* from the documentation of actually exported stuff. If someone says:
*
* ```
* type MyPrivateAlias = ...
*
* function f(a: MyPrivateAlias) {
*
* }
* ```
*
* Then the documentation for f should document the value of MyPrivateAlias. We
* create a ReflectionType for each missing export and stick them in a
* SymbolToType map which we add to the application. In renderType.ts, if we
* have a reference type we check if it's in the SymbolToType map and if so we
* can use the reflection in place of the reference.
*
* More or less unrelatedly, we also add the --sphinxJsConfig option to the
* options parser so we can pass the sphinxJsConfig on the command line.
*/
import {
Application,
Context,
Converter,
DeclarationReflection,
ProjectReflection,
ReferenceType,
Reflection,
ReflectionKind,
SomeType,
} from "typedoc";
import ts from "typescript";
// Map from the Symbol that is the target of the broken reference to the type
// reflection that it should be replaced by. Depending on whether the reference
// type holds a symbolId or a reflection, we use fileName:position or
// fileName:symbolName as the key (respectively). We could always use the
// symbolName but the position is more specific.
type SymbolToTypeKey = `${string}:${number}` | `${string}:${string}`;
export type SymbolToType = Map<SymbolToTypeKey, SomeType>;
export type ReadonlySymbolToType = ReadonlyMap<SymbolToTypeKey, SomeType>;
const ModuleLike: ReflectionKind =
ReflectionKind.Project | ReflectionKind.Module;
function getOwningModule(context: Context): Reflection {
let refl = context.scope;
// Go up the reflection hierarchy until we get to a module
while (!refl.kindOf(ModuleLike)) {
refl = refl.parent!;
}
return refl;
}
/**
* @param app The typedoc app
* @returns The type reference redirect table to be used in renderType.ts
*/
export function redirectPrivateTypes(app: Application): ReadonlySymbolToType {
const referencedSymbols = new Map<Reflection, Set<ts.Symbol>>();
const knownPrograms = new Map<Reflection, ts.Program>();
const symbolToType: SymbolToType = new Map<`${string}:${number}`, SomeType>();
app.converter.on(
Converter.EVENT_CREATE_DECLARATION,
(context: Context, refl: Reflection) => {
// TypeDoc 0.26 doesn't fire EVENT_CREATE_DECLARATION for project
// We need to ensure the project has a program attached to it, so
// do that when the first declaration is created.
if (knownPrograms.size === 0) {
knownPrograms.set(refl.project, context.program);
}
if (refl.kindOf(ModuleLike)) {
knownPrograms.set(refl, context.program);
}
},
);
const tsdocVersion = app.toString().split(" ")[1];
let is28: boolean;
if (
tsdocVersion.startsWith("0.25") ||
tsdocVersion.startsWith("0.26") ||
tsdocVersion.startsWith("0.27")
) {
is28 = false;
} else if (tsdocVersion.startsWith("0.28")) {
is28 = true;
} else {
throw new Error(`Typedoc version ${tsdocVersion} not supported`);
}
let getReflectionFromSymbol = is28
? // @ts-ignore
(context: Context, s: ts.Symbol) => context.getReflectionFromSymbol(s)
: (context: Context, s: ts.Symbol) =>
// @ts-ignore
context.project.getReflectionFromSymbol(s);
/**
* Get the set of ts.symbols referenced from a ModuleReflection or
* ProjectReflection if there is only one file.
*/
function getReferencedSymbols(owningModule: Reflection): Set<ts.Symbol> {
let set = referencedSymbols.get(owningModule);
if (set) {
return set;
}
set = new Set();
referencedSymbols.set(owningModule, set);
return set;
}
function discoverMissingExports(
owningModule: Reflection,
context: Context,
): ts.Symbol[] {
// An export is missing if it was referenced and is not contained in the
// documented
const referenced = getReferencedSymbols(owningModule);
return Array.from(referenced).filter((s) => {
const refl = getReflectionFromSymbol(context, s);
return (
!refl ||
refl.flags.isPrivate ||
refl?.comment?.modifierTags.has("@hidden")
);
});
}
// @ts-ignore
const patchTarget: {
createSymbolReference: (
symbol: ts.Symbol,
context: Context,
name: string,
) => ReferenceType;
} = is28 ? Context.prototype : ReferenceType;
const origCreateSymbolReference = patchTarget.createSymbolReference;
patchTarget.createSymbolReference = function (
symbol: ts.Symbol,
context: Context,
name: string,
) {
const owningModule = getOwningModule(context);
getReferencedSymbols(owningModule).add(symbol);
return origCreateSymbolReference.call(this, symbol, context, name);
};
function onResolveBegin(context: Context): void {
const modules: (DeclarationReflection | ProjectReflection)[] =
context.project.getChildrenByKind(ReflectionKind.Module);
if (modules.length === 0) {
// Single entry point, just target the project.
modules.push(context.project);
}
for (const mod of modules) {
const program = knownPrograms.get(mod);
if (!program) continue;
// Nasty hack here that will almost certainly break in future TypeDoc versions.
context.setActiveProgram(program);
const missing = discoverMissingExports(mod, context);
for (const name of missing) {
const decl = name.declarations![0];
if (decl.getSourceFile().fileName.includes("node_modules")) {
continue;
}
// TODO: maybe handle things other than TypeAliases?
if (ts.isTypeAliasDeclaration(decl)) {
const sf = decl.getSourceFile();
const fileName = sf.fileName;
const converted = context.converter.convertType(context, decl.type);
// Ideally we should be able to key on position rather than file and
// name but I couldn't figure out how.
symbolToType.set(`${fileName}:${decl.name.getText()}`, converted);
}
}
context.setActiveProgram(void 0);
}
}
app.converter.on(Converter.EVENT_RESOLVE_BEGIN, onResolveBegin);
return symbolToType;
}
================================================
FILE: sphinx_js/js/registerImportHook.mjs
================================================
import { register } from "node:module";
register("./importHooks.mjs", import.meta.url);
================================================
FILE: sphinx_js/js/sphinxJsConfig.ts
================================================
import {
Application,
DeclarationReflection,
ParameterReflection,
ProjectReflection,
} from "typedoc";
import { TopLevel } from "./ir.ts";
export type SphinxJsConfig = {
shouldDestructureArg?: (param: ParameterReflection) => boolean;
preConvert?: (app: Application) => Promise<void>;
postConvert?: (
app: Application,
project: ProjectReflection,
typedocToIRMap: ReadonlyMap<DeclarationReflection, TopLevel>,
) => Promise<void>;
};
================================================
FILE: sphinx_js/js/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"noEmit": true,
"allowImportingTsExtensions": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
================================================
FILE: sphinx_js/js/typedocPatches.ts
================================================
/** Declare some extra stuff we monkeypatch on to typedoc */
declare module "typedoc" {
export interface TypeDocOptionMap {
sphinxJsConfig: string;
}
export interface Application {
extraData: {
[key: string]: any;
};
}
}
================================================
FILE: sphinx_js/js/typedocPlugin.ts
================================================
/**
* Typedoc plugin which adds --sphinxJsConfig option
*/
// TODO: we don't seem to resolve imports correctly in this file, but it works
// to do a dynamic import. Figure out why.
export async function load(app: any): Promise<void> {
// @ts-ignore
const typedoc = await import("typedoc");
app.options.addDeclaration({
name: "sphinxJsConfig",
help: "[typedoc-plugin-sphinx-js]: the sphinx-js config",
type: typedoc.ParameterType.String,
});
}
================================================
FILE: sphinx_js/jsdoc.py
================================================
"""JavaScript analyzer
Analyzers run jsdoc or typedoc or whatever, squirrel away their output, and
then lazily constitute IR objects as requested.
"""
import pathlib
import subprocess
from collections import defaultdict
from collections.abc import Callable, Sequence
from errno import ENOENT
from json import dumps, load
from os.path import join, normpath, relpath, sep, splitext
from tempfile import TemporaryFile
from typing import Any, Literal, TypedDict
from sphinx.application import Sphinx
from sphinx.errors import SphinxError
from .analyzer_utils import (
Command,
cache_to_file,
is_explicitly_rooted,
search_node_modules,
)
from .ir import (
NO_DEFAULT,
Attribute,
Class,
DescriptionCode,
Exc,
Function,
Param,
Pathname,
Return,
TopLevel,
)
from .parsers import PathVisitor, path_and_formal_params
from .suffix_tree import SuffixTree
class JsDocCode(TypedDict, total=False):
paramnames: list[str]
class Meta(TypedDict, total=False):
path: str
filename: str
lineno: int
code: JsDocCode
class JsdocType(TypedDict, total=False):
names: list[str]
class Doclet(TypedDict, total=False):
name: str
comment: str
undocumented: bool
access: str
scope: str
meta: Meta
longname: str
memberof: str
description: str
type: JsdocType
classdesc: str
exceptions: list["Doclet"]
returns: list["Doclet"]
examples: list[Any]
see_alsos: list[Any]
properties: list["Doclet"]
params: list["Doclet"]
variable: bool
class Analyzer:
"""A runner of a langauge-specific static analysis tool and translator of
the results to our IR
"""
def __init__(self, json: list[Doclet], base_dir: str):
"""Index and squirrel away the JSON for later lazy conversion to IR
objects.
:arg json: The loaded JSON output from jsdoc
:arg base_dir: Resolve paths in the JSON relative to this directory.
This must be an absolute pathname.
"""
self._base_dir = base_dir
# 2 doclets are made for classes, and they are largely redundant: one
# for the class itself and another for the constructor. However, the
# constructor one gets merged into the class one and is intentionally
# marked as undocumented, even if it isn't. See
# https://github.com/jsdoc3/jsdoc/issues/1129.
doclets = [
doclet
for doclet in json
if doclet.get("comment") and not doclet.get("undocumented")
]
# Build table for lookup by name, which most directives use:
self._doclets_by_path: SuffixTree[Doclet] = SuffixTree()
self._doclets_by_path.add_many(
(full_path_segments(d, base_dir), d) for d in doclets
)
# Build lookup table for autoclass's :members: option. This will also
# pick up members of functions (inner variables), but it will instantly
# filter almost all of them back out again because they're
# undocumented. We index these by unambiguous full path. Then, when
# looking them up by arbitrary name segment, we disambiguate that first
# by running it through the suffix tree above. Expect trouble due to
# jsdoc's habit of calling things (like ES6 class methods)
# "<anonymous>" in the memberof field, even though they have names.
# This will lead to multiple methods having each other's members. But
# if you don't have same-named inner functions or inner variables that
# are documented, you shouldn't have trouble.
self._doclets_by_class = defaultdict(lambda: [])
for d in doclets:
of = d.get("memberof")
if of: # speed optimization
segments = full_path_segments(d, base_dir, longname_field="memberof")
self._doclets_by_class[tuple(segments)].append(d)
@classmethod
def from_disk(
cls, abs_source_paths: list[str], app: Sphinx, base_dir: str
) -> "Analyzer":
json = jsdoc_output(
getattr(app.config, "jsdoc_cache", None),
abs_source_paths,
base_dir,
app.confdir,
getattr(app.config, "jsdoc_config_path", None),
)
return cls(json, base_dir)
def get_object(
self, path_suffix: list[str], as_type: Literal["function", "class", "attribute"]
) -> TopLevel:
"""Return the IR object with the given path suffix.
If helpful, use the ``as_type`` hint, which identifies which autodoc
directive the user called.
"""
# Design note: Originally, I had planned to eagerly convert all the
# doclets to the IR. But it's hard to tell unambiguously what kind
# each doclet is, at least in the case of jsdoc. If instead we lazily
# convert each doclet as it's referenced by an autodoc directive, we
# can use the hint we previously did: the user saying "this is a
# function (by using autofunction on it)", "this is a class", etc.
# Additionally, being lazy lets us avoid converting unused doclets
# altogether.
try:
doclet_as_whatever = {
"function": self._doclet_as_function,
"class": self._doclet_as_class,
"attribute": self._doclet_as_attribute,
}[as_type]
except KeyError:
raise NotImplementedError("Unknown autodoc directive: auto%s" % as_type)
doclet, full_path = self._doclets_by_path.get_with_path(path_suffix)
return doclet_as_whatever(doclet, full_path)
def _doclet_as_class(self, doclet: Doclet, full_path: Sequence[str]) -> Class:
# This is an instance method so it can get at the base dir.
members: list[Function | Attribute] = []
for member_doclet in self._doclets_by_class[tuple(full_path)]:
kind = member_doclet.get("kind")
member_full_path = full_path_segments(member_doclet, self._base_dir)
# Typedefs should still fit into function-shaped holes:
doclet_as_whatever: (
Callable[[Doclet, list[str]], Function]
| Callable[[Doclet, list[str]], Attribute]
) = (
self._doclet_as_function
if (kind == "function" or kind == "typedef")
else self._doclet_as_attribute
)
member = doclet_as_whatever(member_doclet, member_full_path)
members.append(member)
return Class(
description=doclet.get("classdesc", ""),
supers=[], # Could implement for JS later.
exported_from=None, # Could implement for JS later.
is_abstract=False,
interfaces=[],
# Right now, a class generates several doclets, all but one of
# which are marked as undocumented. In the one that's left, most of
# the fields are about the default constructor:
constructor_=self._doclet_as_function(doclet, full_path),
members=members,
**top_level_properties(doclet, full_path, self._base_dir),
)
def _doclet_as_function(self, doclet: Doclet, full_path: Sequence[str]) -> Function:
return Function(
description=description(doclet),
exported_from=None,
is_abstract=False,
is_optional=False,
is_static=is_static(doclet),
is_async=False,
is_private=is_private(doclet),
exceptions=exceptions_to_ir(doclet.get("exceptions", [])),
returns=returns_to_ir(doclet.get("returns", [])),
params=params_to_ir(doclet),
**top_level_properties(doclet, full_path, self._base_dir),
)
def _doclet_as_attribute(
self, doclet: Doclet, full_path: Sequence[str]
) -> Attribute:
return Attribute(
description=description(doclet),
exported_from=None,
is_abstract=False,
is_optional=False,
is_static=False,
is_private=is_private(doclet),
type=get_type(doclet),
**top_level_properties(doclet, full_path, self._base_dir),
)
def is_private(doclet: Doclet) -> bool:
return doclet.get("access") == "private"
def is_static(doclet: Doclet) -> bool:
return doclet.get("scope") == "static"
def full_path_segments(
d: Doclet,
base_dir: str,
longname_field: Literal["longname", "memberof"] = "longname",
) -> list[str]:
"""Return the full, unambiguous list of path segments that points to an
entity described by a doclet.
Example: ``['./', 'dir/', 'dir/', 'file.', 'object.', 'object#', 'object']``
:arg d: The doclet
:arg base_dir: Absolutized value of the root_for_relative_js_paths option
:arg longname_field: The field to look in at the top level of the doclet
for the long name of the object to emit a path to
"""
meta = d["meta"]
rel = relpath(meta["path"], base_dir)
rel = "/".join(rel.split(sep))
rooted_rel = rel if is_explicitly_rooted(rel) else "./%s" % rel
# Building up a string and then parsing it back down again is probably
# not the fastest approach, but it means knowledge of path format is in
# one place: the parser.
path = "{}/{}.{}".format(
rooted_rel, splitext(meta["filename"])[0], d[longname_field]
)
return PathVisitor().visit( # type:ignore[no-any-return]
path_and_formal_params["path"].parse(path)
)
@cache_to_file(lambda cache, *args: cache)
def jsdoc_output(
cache: str | None,
abs_source_paths: list[str],
base_dir: str,
sphinx_conf_dir: str | pathlib.Path,
config_path: str | None = None,
) -> list[Doclet]:
jsdoc = search_node_modules("jsdoc", "jsdoc/jsdoc.js", sphinx_conf_dir)
command = Command("node")
command.add(jsdoc)
command.add("-X", *abs_source_paths)
if config_path:
command.add("-c", normpath(join(sphinx_conf_dir, config_path)))
# Use a temporary file to handle large output volume. JSDoc defaults to
# utf8-encoded output.
with TemporaryFile(mode="w+b") as temp:
try:
subprocess.run(
command.make(), cwd=sphinx_conf_dir, stdout=temp, encoding="utf8"
)
except OSError as exc:
if exc.errno == ENOENT:
raise SphinxError(
'%s was not found. Install it using "npm install -g jsdoc".'
% command.program
)
else:
raise
# Once output is finished, move back to beginning of file and load it:
temp.seek(0)
try:
return load(temp) # type:ignore[no-any-return]
except ValueError:
raise SphinxError(
"jsdoc found no JS files in the directories %s. Make sure js_source_path is set correctly in conf.py. It is also possible (though unlikely) that jsdoc emitted invalid JSON."
% abs_source_paths
)
def format_default_according_to_type_hints(
value: Any, declared_types: str | None, first_type_is_string: bool
) -> Any:
"""Return the default value for a param, formatted as a string
ready to be used in a formal parameter list.
JSDoc is a mess at extracting default values. It can unambiguously
extract only a few simple types from the function signature, and
ambiguity is even more rife when extracting from doclets. So we use
any declared types to resolve the ambiguity.
:arg value: The extracted value, which may be of the right or wrong type
:arg declared_types: A list of types declared in the doclet for
this param. For example ``{string|number}`` would yield ['string',
'number'].
:arg first_type_is_string: Whether the first declared type for this param
is string, which we use as a signal that any string-typed default value
in the JSON is legitimately string-typed rather than some arrow
function or something just encased in quotes because they couldn't
think what else to do. Thus, if you want your ambiguously documented
default like ``@param {string|Array} [foo=[]]`` to be treated as a
string, make sure "string" comes first.
"""
if isinstance(value, str): # JSDoc threw it to us as a string in the JSON.
if declared_types and not first_type_is_string:
# It's a spurious string, like ``() => 5`` or a variable name.
# Let it through verbatim.
return value
else:
# It's a real string.
return dumps(value) # Escape any contained quotes.
else: # It came in as a non-string.
if first_type_is_string:
# It came in as an int, null, or bool, and we have to
# convert it back to a string.
return f'"{dumps(value)}"'
else:
# It's fine as the type it is.
return dumps(value)
def description(obj: Doclet) -> str:
return obj.get("description", "")
def get_type(props: Doclet) -> str | None:
"""Given an arbitrary object from a jsdoc-emitted JSON file, go get the
``type`` property, and return the textual rendering of the type, possibly a
union like ``Foo | Bar``, or None if we don't know the type."""
names = props.get("type", {}).get("names", [])
return "|".join(names) if names else None
def top_level_properties(
doclet: Doclet, full_path: Sequence[str], base_dir: str
) -> dict[str, Any]:
"""Extract information common to complex entities, and return it as a dict.
Specifically, pull out the information needed to parametrize TopLevel's
constructor.
"""
return dict(
name=doclet["name"],
path=Pathname(full_path),
filename=doclet["meta"]["filename"],
deppath=relpath(
join(doclet["meta"]["path"], doclet["meta"]["filename"]), base_dir
),
# description's source varies depending on whether the doclet is a
# class, so it gets filled out elsewhere.
line=doclet["meta"]["lineno"],
deprecated=doclet.get("deprecated", False),
examples=[
[DescriptionCode("```js\n" + x + "\n```")]
for x in doclet.get("examples", [])
],
see_alsos=doclet.get("see", []),
properties=properties_to_ir(doclet.get("properties", [])),
)
def properties_to_ir(properties: list[Doclet]) -> list[Attribute]:
"""Turn jsdoc-emitted properties JSON into a list of Properties."""
return [
Attribute(
type=get_type(p),
name=p["name"],
# We can get away with setting null values for these
# because we never use them for anything:
path=Pathname([]),
filename="",
deppath="",
description=description(p),
line=0,
deprecated=False,
examples=[],
see_alsos=[],
properties=[],
exported_from=None,
is_abstract=False,
is_optional=False,
is_static=False,
is_private=False,
)
for p in properties
]
def first_type_is_string(type: JsdocType) -> bool:
type_names = type.get("names", [])
return bool(type_names) and type_names[0] == "string"
def params_to_ir(doclet: Doclet) -> list[Param]:
"""Extract the parameters of a function or class, and return a list of
Param instances.
Formal param fallback philosophy:
1. If the user puts a formal param list in the RST explicitly, use that.
2. Else, if they've @param'd anything, show just those args. This gives the
user full control from the code, so they can use autoclass without
having to manually write each function signature in the RST.
3. Else, extract a formal param list from the meta field, which will lack
descriptions.
Param list:
* Don't show anything without a description or at least a type. It adds
nothing.
Our extraction to IR thus follows our formal param philosophy, and the
renderer caps it off by checking for descriptions and types while building
the param+description list.
:arg doclet: A JSDoc doclet representing a function or class
"""
ret = []
# First, go through the explicitly documented params:
for p in doclet.get("params", []):
type = get_type(p)
default = p.get("defaultvalue", NO_DEFAULT)
formatted_default = (
NO_DEFAULT
if default is NO_DEFAULT
else format_default_according_to_type_hints(
default, type, first_type_is_string(p.get("type", {}))
)
)
ret.append(
Param(
name=p["name"],
description=description(p),
has_default=default is not NO_DEFAULT,
default=formatted_default,
is_variadic=p.get("variable", False),
type=get_type(p),
)
)
# Use params from JS code if there are no documented @params.
if not ret:
ret = [Param(name=p) for p in doclet["meta"]["code"].get("paramnames", [])]
return ret
def exceptions_to_ir(exceptions: list[Doclet]) -> list[Exc]:
"""Turn jsdoc's JSON-formatted exceptions into a list of Exceptions."""
return [Exc(type=get_type(e), description=description(e)) for e in exceptions]
def returns_to_ir(returns: list[Doclet]) -> list[Return]:
return [Return(type=get_type(r), description=description(r)) for r in returns]
================================================
FILE: sphinx_js/parsers.py
================================================
from re import sub
from parsimonious import Grammar, NodeVisitor
path_and_formal_params = Grammar(
r"""
path_and_formal_params = path formal_params
# Invalid JS symbol names and wild-and-crazy placement of slashes later in
# the path (after the FS path is over) will be caught at name-resolution
# time.
#
# Note that "." is a non-separator char only when used in ./ and ../
# prefixes.
path = relative_dir* middle_segments name
# A name is a series of non-separator (not ~#/.), non-(, and backslashed
# characters.
name = ~r"(?:[^(/#~.\\]|\\.)+"
sep = ~r"[#~/.]"
relative_dir = "./" / "../"
middle_segments = name_and_sep*
name_and_sep = name sep
formal_params = ~r".*"
"""
)
class PathVisitor(NodeVisitor): # type:ignore[type-arg]
grammar = path_and_formal_params
def visit_path_and_formal_params(self, node, children):
return children
def visit_name(self, node, children):
# This, for better or worse, also makes Python string escape sequences,
# like \n for newline, work.
return _backslash_unescape(node.text)
def visit_sep(self, node, children):
return node.text
def visit_path(self, node, children):
relative_dirs, middle_segments, name = children
segments = relative_dirs[:]
segments.extend(middle_segments)
segments.append(name)
return segments
def visit_name_and_sep(self, node, children):
"""Concatenate name and separator into one string."""
return "".join(x for x in children)
def visit_formal_params(self, node, children):
return node.text
def visit_relative_dir(self, node, children):
"""Return './' or '../'."""
return node.text
def visit_cur_dir(self, node, children):
return node.text
def visit_middle_segments(self, node, children):
return children
def generic_visit(self, node, visited_children):
"""``relative_dir*`` has already turned each item of ``children`` into
'./' or '../'. Just pass the list of those through."""
return visited_children
def _backslash_unescape(str):
"""Return a string with backslash escape sequences replaced with their
literal meanings.
Don't respect any of the conventional \n, \v, \t, etc. Keep it simple. Keep
it safe.
"""
return sub(r"\\(.)", lambda match: match.group(1), str)
================================================
FILE: sphinx_js/py.typed
================================================
================================================
FILE: sphinx_js/renderers.py
================================================
import textwrap
from collections.abc import Callable, Iterable, Iterator, Sequence
from functools import partial
from re import sub
from typing import Any, Literal, Protocol, TypeVar
from docutils import nodes
from docutils.nodes import Node
from docutils.parsers.rst import Directive
from docutils.parsers.rst import Parser as RstParser
from docutils.statemachine import StringList
from jinja2 import Environment, PackageLoader
from sphinx import addnodes
from sphinx import version_info as sphinx_version_info
from sphinx.application import Sphinx
from sphinx.config import Config
from sphinx.errors import SphinxError
from sphinx.ext.autosummary import autosummary_table, extract_summary
from sphinx.util import logging, rst
from sphinx.util.docutils import switch_source_input
from sphinx_js import ir
from .analyzer_utils import dotted_path
from .ir import (
Attribute,
Class,
DescriptionName,
DescriptionText,
Exc,
Function,
Interface,
Module,
Param,
Pathname,
Return,
TopLevel,
Type,
TypeAlias,
TypeParam,
TypeXRef,
TypeXRefInternal,
)
from .jsdoc import Analyzer as JsAnalyzer
from .parsers import PathVisitor
from .suffix_tree import SuffixAmbiguous, SuffixNotFound
from .typedoc import Analyzer as TsAnalyzer
Analyzer = TsAnalyzer | JsAnalyzer
logger = logging.getLogger(__name__)
def new_document_from_parent(
source_path: str, parent_doc: nodes.document, line: int | None = None
) -> nodes.document:
"""Create a new document that inherits the parent's settings and reporter."""
settings = parent_doc.settings
reporter = parent_doc.reporter
doc = nodes.document(settings, reporter, source=source_path)
doc.note_source(source_path, -1)
# Store line number for sphinx_js_type_role to use
doc.sphinx_js_source_line = line # type: ignore[attr-defined]
return doc
def sort_attributes_first_then_by_path(obj: TopLevel) -> Any:
"""Return a sort key for IR objects."""
match obj:
case Attribute(_):
idx = 0
case Function(_):
idx = 1
case Class(_) | Interface(_):
idx = 2
return idx, obj.path.segments
def _members_to_include_inner(
members: Iterable[TopLevel],
include: list[str],
) -> list[TopLevel]:
"""Return the members that should be included (before excludes and
access specifiers are taken into account).
This will either be the ones explicitly listed after the
``:members:`` option, in that order; all members of the class; or
listed members with remaining ones inserted at the placeholder "*".
"""
if not include:
# Specifying none means listing all.
return sorted(members, key=sort_attributes_first_then_by_path)
included_set = set(include)
# If the special name * is included in the list, include all other
# members, in sorted order.
if "*" in included_set:
star_index = include.index("*")
sorted_not_included_members = sorted(
(m for m in members if m.name not in included_set),
key=sort_attributes_first_then_by_path,
)
not_included = [m.name for m in sorted_not_included_members]
include = include[:star_index] + not_included + include[star_index + 1 :]
included_set.update(not_included)
# Even if there are 2 members with the same short name (e.g. a
# static member and an instance one), keep them both. This
# prefiltering step should make the below sort less horrible, even
# though I'm calling index().
included_members = [m for m in members if m.name in included_set]
# sort()'s stability should keep same-named members in the order
# JSDoc spits them out in.
included_members.sort(key=lambda m: include.index(m.name))
return included_members
def members_to_include(
members: Iterable[TopLevel],
include: list[str],
exclude: list[str],
should_include_private: bool,
) -> Iterator[TopLevel]:
for member in _members_to_include_inner(members, include):
if member.name in exclude:
continue
if not should_include_private and getattr(member, "is_private", False):
continue
yield member
def unwrapped(text: str) -> str:
"""Return the text with line wrapping removed."""
return sub(r"[ \t]*[\r\n]+[ \t]*", " ", text)
def render_description(description: ir.Description) -> str:
"""Construct a single comment string from a fancy object."""
if isinstance(description, str):
return description
content = []
prev = ""
for s in description:
if isinstance(s, DescriptionName):
prev = s.text
content.append(prev + "\n")
continue
if isinstance(s, DescriptionText):
prev = s.text
content.append(prev)
continue
# code
if s.code.startswith("```") and s.code.count("\n") >= 1:
# A code pen
first_line, rest = s.code.split("\n", 1)
rest = rest.removesuffix("```")
code_type = first_line.removeprefix("```")
start = f".. code-block:: {code_type}\n\n"
codeblock = textwrap.indent(rest, " " * 4)
end = "\n\n"
content.append("\n" + start + codeblock + end)
continue
if s.code.startswith("``"):
# Sphinx-style escaped, leave it alone.
content.append(s.code)
continue
if prev.endswith(":"):
# A sphinx role, leave it alone
content.append(s.code)
continue
if prev.endswith(" ") and not s.code.endswith(">`"):
# Used single uptick with code, put double upticks
content.append(f"`{s.code}`")
continue
content.append(s.code)
return "".join(content)
R = TypeVar("R", bound="Renderer")
class HasDepPath(Protocol):
deppath: str | None
class Renderer:
_type_xref_formatter: Callable[[TypeXRef], str]
# We turn the <span class="sphinx_js-type"> in the analyzer tests because it
# makes a big mess.
_add_span: bool
_partial_path: list[str]
_explicit_formal_params: str
_content: list[str] | StringList
_options: dict[str, Any]
def _parse_path(self, arg: str) -> None:
# content, arguments, options, app: all need to be accessible to
# template_vars, so we bring them in on construction and stow them away
# on the instance so calls to template_vars don't need to concern
# themselves with what it needs.
(
self._partial_path,
self._explicit_formal_params,
) = PathVisitor().parse(arg)
def __init__(
self,
directive: Directive,
app: Sphinx,
arguments: list[str],
content: list[str] | StringList | None = None,
options: dict[str, Any] | None = None,
):
self._add_span = True
# Fix crash when calling eval_rst with CommonMarkParser:
if not hasattr(directive.state.document.settings, "tab_width"):
directive.state.document.settings.tab_width = 8
self._directive = directive
self._app = app
self._set_type_xref_formatter(app.config.ts_type_xref_formatter)
self._parse_path(arguments[0])
self._content = content or StringList()
self._options = options or {}
@classmethod
def from_directive(cls: type[R], directive: Directive, app: Sphinx) -> R:
"""Return one of these whose state is all derived from a directive.
This is suitable for top-level calls but not for when a renderer is
being called from a different renderer, lest content and such from the
outer directive be duplicated in the inner directive.
:arg directive: The associated Sphinx directive
:arg app: The Sphinx global app object. Some methods need this.
"""
return cls(
directive,
app,
arguments=directive.arguments,
content=directive.content,
options=directive.options,
)
def _set_type_xref_formatter(
self, formatter: Callable[[Config, TypeXRef], str] | None
) -> None:
if formatter:
self._type_xref_formatter = partial(formatter, self._app.config)
return
def default_type_xref_formatter(xref: TypeXRef) -> str:
return xref.name
self._type_xref_formatter = default_type_xref_formatter
def get_object(self) -> HasDepPath:
raise NotImplementedError
def dependencies(self) -> set[str]:
"""Return a set of path(s) to the file(s) that the IR object
rendered by this renderer is from. Each path is absolute or
relative to `root_for_relative_js_paths`.
"""
try:
obj = self.get_object()
if obj.deppath:
return set([obj.deppath])
except SphinxError as exc:
logger.exception("Exception while retrieving paths for IR object: %s" % exc)
return set([])
def rst_nodes(self) -> list[Node]:
raise NotImplementedError
class JsRenderer(Renderer):
"""Abstract superclass for renderers of various sphinx-js directives
Provides an inversion-of-control framework for rendering and bridges us
from the hidden, closed-over JsDirective subclasses to top-level classes
that can see and use each other. Handles parsing of a single, all-consuming
argument that consists of a JS/TS entity reference and an optional formal
parameter list.
"""
_renderer_type: Literal["function", "class", "attribute"]
_template: str
def _template_vars(self, name: str, obj: TopLevel) -> dict[str, Any]:
raise NotImplementedError
def lookup_object(
self,
partial_path: list[str],
renderer_type: Literal["function", "class", "attribute"] = "attribute",
) -> TopLevel:
try:
analyzer: Analyzer = (
self._app._sphinxjs_analyzer # type:ignore[attr-defined]
)
obj = analyzer.get_object(partial_path, renderer_type)
return obj
except SuffixNotFound as exc:
raise SphinxError(
'No documentation was found for object "%s" or any path ending with that.'
% "".join(exc.segments)
)
except SuffixAmbiguous as exc:
raise SphinxError(
'More than one object matches the path suffix "{}". Candidate paths have these segments in front: {}'.format(
"".join(exc.segments), exc.next_possible_keys
)
)
def get_object(self) -> TopLevel:
"""Return the IR object rendered by this renderer."""
return self.lookup_object(self._partial_path, self._renderer_type)
def rst_nodes(self) -> list[Node]:
"""Render into RST nodes a thing shaped like a function, having a name
and arguments.
Fill in args, docstrings, and info fields from stored JSDoc output.
"""
obj = self.get_object()
rst = self.rst(
self._partial_path, obj, use_short_name="short-name" in self._options
)
# Parse the RST into docutils nodes with a fresh doc, and return
# them. Use the directive's source location for error messages.
source, line = self._directive.state_machine.get_source_and_line(
self._directive.lineno
)
doc = new_document_from_parent(
source or "", self._directive.state.document, line
)
RstParser().parse(rst, doc)
return doc.children
def rst_for(self, obj: TopLevel) -> str:
renderer_class: type
match obj:
case Attribute(_) | TypeAlias(_):
renderer_class = AutoAttributeRenderer
case Function(_):
renderer_class = AutoFunctionRenderer
case Class(_) | Interface(_):
renderer_class = AutoClassRenderer
case _:
raise RuntimeError("This shouldn't happen...")
renderer = renderer_class(
self._directive, self._app, arguments=["dummy"], options={"members": ["*"]}
)
return renderer.rst([obj.name], obj, use_short_name=False)
def rst(
self, partial_path: list[str], obj: TopLevel, use_short_name: bool = False
) -> str:
"""Return rendered RST about an entity with the given name and IR
object."""
dotted_name = partial_path[-1] if use_short_name else dotted_path(partial_path)
# Render to RST using Jinja:
env = Environment(loader=PackageLoader("sphinx_js", "templates"))
template = env.get_template(self._template)
result = template.render(**self._template_vars(dotted_name, obj))
result = result.strip()
had_blank = False
lines = []
for line in result.splitlines():
if line.strip():
had_blank = False
lines.append(line.rstrip())
elif not had_blank:
lines.append("")
had_blank = True
result = "\n".join(lines) + "\n"
return result
def _type_params(self, obj: Function | Class | TypeAlias | Interface) -> str:
if not obj.type_params:
return ""
return "<{}>".format(", ".join(tp.name for tp in obj.type_params))
def _formal_params(self, obj: Function) -> str:
"""Return the JS function params, looking first to any explicit params
written into the directive and falling back to those in comments or JS
code.
Return a ReST-escaped string ready for substitution into the template.
"""
if self._explicit_formal_params:
return self._explicit_formal_params
formals = []
used_names = set()
for param in obj.params:
# Turn "@param p2.subProperty" into just p2. We wouldn't want to
# add subproperties to the flat formal param list:
name = param.name.split(".")[0]
# Add '...' to the parameter name if it's a variadic argument
if param.is_variadic:
name = "..." + name
if name not in used_names:
# We don't rst.escape() anything here, because, empirically,
# the js:function directive (or maybe directive params in
# general) automatically ignores markup constructs in its
# parameter (though not its contents).
formals.append(
name if not param.has_default else f"{name}={param.default}"
)
used_names.add(name)
return "({})".format(", ".join(formals))
def render_type(self, type: Type, escape: bool = False, bold: bool = True) -> str:
if not type:
return ""
if isinstance(type, str):
if bold:
type = "**%s**" % type
if escape:
type = rst.escape(type)
return type
it = iter(type)
def strs() -> Iterator[str]:
for elem in it:
if isinstance(elem, str):
yield elem
else:
xref.append(elem)
return
res = []
while True:
xref: list[TypeXRef] = []
s = "".join(strs())
if escape:
s = rst.escape(s)
if s:
res.append(s)
if not xref:
break
res.append(self.render_xref(xref[0], escape))
joined = r"\ ".join(res)
if self._add_span:
return f":sphinx_js_type:`{rst.escape(joined)}`"
return joined
def render_xref(self, s: TypeXRef, escape: bool = False) -> str:
obj = None
if isinstance(s, TypeXRefInternal):
try:
obj = self.lookup_object(s.path)
# Stick the kind on the xref so that the formatter will know what
# xref role to emit. I'm not sure how to compute this earlier. It's
# convenient to do it here.
s.kind = type(obj).__name__.lower()
except SphinxError:
# This sometimes happens on the code path in
# convertReferenceToXRef when we generate an xref internal from
# a symbolId. That code path is probably entirely wrong.
# TODO: fix and add test coverage.
pass
result = self._type_xref_formatter(s)
if escape:
result = rst.escape(result)
return result
def _return_formatter(self, return_: Return) -> tuple[list[str], str]:
"""Derive heads and tail from ``@returns`` blocks."""
tail = []
if return_.type:
tail.append(self.render_type(return_.type, escape=False))
if return_.description:
tail.append(render_description(return_.description))
return ["returns"], " -- ".join(tail)
def _type_param_formatter(self, tparam: TypeParam) -> tuple[list[str], str] | None:
v = tparam.name
descr = render_description(tparam.description)
if tparam.extends:
descr += " (extends " + self.render_type(tparam.extends) + ")"
heads = ["typeparam", v]
return heads, descr
def _param_formatter(self, param: Param) -> tuple[list[str], str] | None:
"""Derive heads and tail from ``@param`` blocks."""
if not param.type and not param.description:
# There's nothing worth saying about this param.
return None
heads = ["param"]
heads.append(param.name)
tail = render_description(param.description)
return heads, tail
def _param_type_formatter(self, param: Param) -> tuple[list[str], str] | None:
"""Generate types for function parameters specified in field."""
if not param.type:
return None
heads = ["type", param.name]
tail = self.render_type(param.type)
return heads, tail
def _exception_formatter(self, exception: Exc) -> tuple[list[str], str]:
"""Derive heads and tail from ``@throws`` blocks."""
heads = ["throws"]
if exception.type:
heads.append(self.render_type(exception.type, bold=False))
tail = render_description(exception.description)
return heads, tail
def _fields(self, obj: TopLevel) -> Iterator[tuple[list[str], str]]:
"""Return an iterable of "info fields" to be included in the directive,
like params, return values, and exceptions.
Each field consists of a tuple ``(heads, tail)``, where heads are
words that go between colons (as in ``:param string href:``) and
tail comes after.
"""
FIELD_TYPES: list[tuple[str, Callable[[Any], tuple[list[str], str] | None]]] = [
("type_params", self._type_param_formatter),
("params", self._param_formatter),
("params", self._param_type_formatter),
("properties", self._param_formatter),
("properties", self._param_type_formatter),
("exceptions", self._exception_formatter),
("returns", self._return_formatter),
]
for collection_attr, callback in FIELD_TYPES:
for instance in getattr(obj, collection_attr, []):
result = callback(instance)
if not result:
continue
heads, tail = result
# If there are line breaks in the tail, the RST parser will
# end the field list prematurely.
#
# TODO: Instead, indent multi-line tails juuuust right, and
# we can enjoy block-lev
gitextract_jnq5w43z/
├── .editorconfig
├── .github/
│ ├── codecov.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── check_ts.yml
│ ├── ci.yml
│ ├── release.yml
│ └── test_report.yml
├── .gitignore
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTORS
├── LICENSE
├── README.md
├── biome.json
├── noxfile.py
├── pyproject.toml
├── sphinx_js/
│ ├── __init__.py
│ ├── analyzer_utils.py
│ ├── directives.py
│ ├── ir.py
│ ├── js/
│ │ ├── cli.ts
│ │ ├── convertTopLevel.ts
│ │ ├── convertType.ts
│ │ ├── importHooks.mjs
│ │ ├── ir.ts
│ │ ├── main.ts
│ │ ├── package.json
│ │ ├── redirectPrivateAliases.ts
│ │ ├── registerImportHook.mjs
│ │ ├── sphinxJsConfig.ts
│ │ ├── tsconfig.json
│ │ ├── typedocPatches.ts
│ │ └── typedocPlugin.ts
│ ├── jsdoc.py
│ ├── parsers.py
│ ├── py.typed
│ ├── renderers.py
│ ├── suffix_tree.py
│ ├── templates/
│ │ ├── attribute.rst
│ │ ├── class.rst
│ │ ├── common.rst
│ │ └── function.rst
│ └── typedoc.py
└── tests/
├── __init__.py
├── conftest.py
├── roots/
│ ├── test-incremental_js/
│ │ ├── a.js
│ │ ├── a.rst
│ │ ├── a_b.rst
│ │ ├── b.rst
│ │ ├── conf.py
│ │ ├── index.rst
│ │ ├── inner/
│ │ │ └── b.js
│ │ ├── jsdoc.json
│ │ └── unrelated.rst
│ └── test-incremental_ts/
│ ├── a.rst
│ ├── a.ts
│ ├── a_b.rst
│ ├── b.rst
│ ├── conf.py
│ ├── index.rst
│ ├── inner/
│ │ └── b.ts
│ ├── tsconfig.json
│ └── unrelated.rst
├── sphinxJsConfig.ts
├── test.ts
├── test_build_js/
│ ├── source/
│ │ ├── code.js
│ │ ├── docs/
│ │ │ ├── autoattribute.rst
│ │ │ ├── autoattribute_deprecated.rst
│ │ │ ├── autoattribute_example.rst
│ │ │ ├── autoattribute_see.rst
│ │ │ ├── autoclass.rst
│ │ │ ├── autoclass_alphabetical.rst
│ │ │ ├── autoclass_deprecated.rst
│ │ │ ├── autoclass_example.rst
│ │ │ ├── autoclass_exclude_members.rst
│ │ │ ├── autoclass_members.rst
│ │ │ ├── autoclass_members_list.rst
│ │ │ ├── autoclass_members_list_star.rst
│ │ │ ├── autoclass_no_paramnames.rst
│ │ │ ├── autoclass_private_members.rst
│ │ │ ├── autoclass_see.rst
│ │ │ ├── autofunction_callback.rst
│ │ │ ├── autofunction_defaults_code.rst
│ │ │ ├── autofunction_defaults_doclet.rst
│ │ │ ├── autofunction_deprecated.rst
│ │ │ ├── autofunction_destructured_params.rst
│ │ │ ├── autofunction_example.rst
│ │ │ ├── autofunction_explicit.rst
│ │ │ ├── autofunction_long.rst
│ │ │ ├── autofunction_minimal.rst
│ │ │ ├── autofunction_see.rst
│ │ │ ├── autofunction_short.rst
│ │ │ ├── autofunction_static.rst
│ │ │ ├── autofunction_typedef.rst
│ │ │ ├── autofunction_variadic.rst
│ │ │ ├── avoid_shadowing.rst
│ │ │ ├── conf.py
│ │ │ ├── getter_setter.rst
│ │ │ ├── index.rst
│ │ │ ├── injection.rst
│ │ │ ├── union.rst
│ │ │ └── unwrapped.rst
│ │ └── more_code.js
│ └── test_build_js.py
├── test_build_ts/
│ ├── source/
│ │ ├── class.ts
│ │ ├── docs/
│ │ │ ├── async_function.rst
│ │ │ ├── autoclass_class_with_interface_and_supers.rst
│ │ │ ├── autoclass_constructorless.rst
│ │ │ ├── autoclass_exported.rst
│ │ │ ├── autoclass_interface_optionals.rst
│ │ │ ├── autoclass_star.rst
│ │ │ ├── automodule.rst
│ │ │ ├── autosummary.rst
│ │ │ ├── conf.py
│ │ │ ├── deprecated.rst
│ │ │ ├── example.rst
│ │ │ ├── getset.rst
│ │ │ ├── index.rst
│ │ │ ├── inherited_docs.rst
│ │ │ ├── predicate.rst
│ │ │ ├── sphinx_link_in_description.rst
│ │ │ ├── symbol.rst
│ │ │ └── xrefs.rst
│ │ ├── empty.ts
│ │ ├── module.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ └── test_build_ts.py
├── test_build_xref_none/
│ ├── source/
│ │ ├── docs/
│ │ │ ├── conf.py
│ │ │ └── index.rst
│ │ └── main.ts
│ └── test_build_xref_none.py
├── test_common_mark/
│ ├── source/
│ │ ├── code.js
│ │ └── docs/
│ │ ├── conf.py
│ │ └── index.md
│ └── test_common_mark.py
├── test_dot_dot_paths/
│ ├── source/
│ │ ├── code.js
│ │ └── docs/
│ │ ├── conf.py
│ │ └── index.rst
│ └── test_dot_dot_paths.py
├── test_incremental.py
├── test_init.py
├── test_ir.py
├── test_jsdoc_analysis/
│ ├── source/
│ │ ├── class.js
│ │ └── function.js
│ └── test_jsdoc.py
├── test_parsers.py
├── test_paths.py
├── test_renderers.py
├── test_suffix_tree.py
├── test_testing.py
├── test_typedoc_analysis/
│ ├── source/
│ │ ├── exports.ts
│ │ ├── nodes.ts
│ │ ├── subdir/
│ │ │ └── pathSegments.ts
│ │ ├── tsconfig.json
│ │ └── types.ts
│ └── test_typedoc_analysis.py
└── testing.py
SYMBOL INDEX (709 symbols across 57 files)
FILE: noxfile.py
function tests (line 11) | def tests(session: Session) -> None:
function typecheck_ts (line 28) | def typecheck_ts(session: Session, typedoc: str) -> None:
function test_typedoc (line 41) | def test_typedoc(session: Session, typedoc: str) -> None:
function test_sphinx_6 (line 82) | def test_sphinx_6(session: Session) -> None:
FILE: sphinx_js/__init__.py
function make_css_file (line 18) | def make_css_file(app: Sphinx) -> None:
function on_build_finished (line 32) | def on_build_finished(app: Sphinx, exc: Exception | None) -> None:
function setup (line 38) | def setup(app: Sphinx) -> None:
function analyze (line 72) | def analyze(app: Sphinx) -> None:
function root_or_fallback (line 105) | def root_or_fallback(
FILE: sphinx_js/analyzer_utils.py
function program_name_on_this_platform (line 14) | def program_name_on_this_platform(program: str) -> str:
function search_node_modules (line 21) | def search_node_modules(cmdname: str, cmdpath: str, dir: str | Path) -> ...
class Command (line 44) | class Command:
method __init__ (line 45) | def __init__(self, program: str):
method add (line 49) | def add(self, *args: str) -> None:
method make (line 52) | def make(self) -> list[str]:
function cache_to_file (line 60) | def cache_to_file(
function is_explicitly_rooted (line 88) | def is_explicitly_rooted(path: str) -> bool:
function dotted_path (line 99) | def dotted_path(segments: Sequence[str]) -> str:
FILE: sphinx_js/directives.py
function unescape (line 50) | def unescape(escaped: str) -> str:
function _members_to_exclude (line 60) | def _members_to_exclude(arg: str | None) -> set[str]:
function sphinx_js_type_role (line 70) | def sphinx_js_type_role( # type: ignore[no-untyped-def]
class JSXrefMixin (line 99) | class JSXrefMixin:
method make_xref (line 100) | def make_xref(
class JSTypedField (line 125) | class JSTypedField(JSXrefMixin, TypedField):
class JSGroupedField (line 129) | class JSGroupedField(JSXrefMixin, GroupedField):
function fix_js_make_xref (line 135) | def fix_js_make_xref() -> None:
function fix_staticfunction_objtype (line 168) | def fix_staticfunction_objtype() -> None:
function add_type_param_field_to_directives (line 177) | def add_type_param_field_to_directives() -> None:
class JsDirective (line 190) | class JsDirective(Directive):
method _run (line 200) | def _run(self, renderer_class: type[Renderer], app: Sphinx) -> list[No...
class JsDirectiveWithChildren (line 206) | class JsDirectiveWithChildren(JsDirective):
function note_dependencies (line 219) | def note_dependencies(app: Sphinx, dependencies: Iterable[str]) -> None:
function auto_function_directive_bound_to_app (line 235) | def auto_function_directive_bound_to_app(app: Sphinx) -> type[Directive]:
function auto_class_directive_bound_to_app (line 250) | def auto_class_directive_bound_to_app(app: Sphinx) -> type[Directive]:
function auto_attribute_directive_bound_to_app (line 266) | def auto_attribute_directive_bound_to_app(app: Sphinx) -> type[Directive]:
class desc_js_type_parameter_list (line 280) | class desc_js_type_parameter_list(nodes.Part, nodes.Inline, nodes.FixedT...
method astext (line 289) | def astext(self) -> str:
function html5_visit_desc_js_type_parameter_list (line 293) | def html5_visit_desc_js_type_parameter_list(
function html5_depart_desc_js_type_parameter_list (line 313) | def html5_depart_desc_js_type_parameter_list(
function text_visit_desc_js_type_parameter_list (line 326) | def text_visit_desc_js_type_parameter_list(
function text_depart_desc_js_type_parameter_list (line 337) | def text_depart_desc_js_type_parameter_list(
function latex_visit_desc_type_parameter_list (line 347) | def latex_visit_desc_type_parameter_list(
function latex_depart_desc_type_parameter_list (line 353) | def latex_depart_desc_type_parameter_list(
function add_param_list_to_signode (line 359) | def add_param_list_to_signode(signode: desc_signature, params: str) -> N...
function handle_typeparams_for_signature (line 366) | def handle_typeparams_for_signature(
class JSFunction (line 395) | class JSFunction(JSCallable):
method get_display_prefix (line 404) | def get_display_prefix(
method handle_signature (line 418) | def handle_signature(self, sig: str, signode: desc_signature) -> tuple...
class JSInterface (line 422) | class JSInterface(JSCallable):
method get_display_prefix (line 430) | def get_display_prefix(self) -> list[Node]:
method handle_signature (line 436) | def handle_signature(self, sig: str, signode: desc_signature) -> tuple...
class JSTypeAlias (line 440) | class JSTypeAlias(JSObject):
method handle_signature (line 450) | def handle_signature(self, sig: str, signode: desc_signature) -> tuple...
class JSClass (line 454) | class JSClass(JSConstructor):
method handle_signature (line 455) | def handle_signature(self, sig: str, signode: desc_signature) -> tuple...
function patch_JsObject_get_index_text (line 460) | def patch_JsObject_get_index_text() -> None:
function auto_module_directive_bound_to_app (line 475) | def auto_module_directive_bound_to_app(app: Sphinx) -> type[Directive]:
function auto_summary_directive_bound_to_app (line 485) | def auto_summary_directive_bound_to_app(app: Sphinx) -> type[Directive]:
function add_directives (line 495) | def add_directives(app: Sphinx) -> None:
FILE: sphinx_js/ir.py
class TypeXRefIntrinsic (line 38) | class TypeXRefIntrinsic:
class TypeXRefInternal (line 44) | class TypeXRefInternal:
class TypeXRefExternal (line 52) | class TypeXRefExternal:
class DescriptionName (line 65) | class DescriptionName:
class DescriptionText (line 71) | class DescriptionText:
class DescriptionCode (line 77) | class DescriptionCode:
class Pathname (line 90) | class Pathname:
method __init__ (line 97) | def __init__(self, segments: Sequence[str]):
method __str__ (line 100) | def __str__(self) -> str:
method __repr__ (line 103) | def __repr__(self) -> str:
method __eq__ (line 106) | def __eq__(self, other: Any) -> bool:
method dotted (line 109) | def dotted(self) -> str:
class _NoDefault (line 114) | class _NoDefault:
method __repr__ (line 121) | def __repr__(self) -> str:
class _Member (line 129) | class _Member:
class TypeParam (line 147) | class TypeParam:
class Param (line 154) | class Param:
method __attrs_post_init__ (line 172) | def __attrs_post_init__(self) -> None:
class Exc (line 180) | class Exc:
class Return (line 189) | class Return:
class Module (line 198) | class Module:
class TopLevel (line 211) | class TopLevel:
class Attribute (line 268) | class Attribute(TopLevel, _Member):
class Function (line 283) | class Function(TopLevel, _Member):
class _MembersAndSupers (line 295) | class _MembersAndSupers:
class Interface (line 309) | class Interface(TopLevel, _MembersAndSupers):
class Class (line 317) | class Class(TopLevel, _MembersAndSupers):
class TypeAlias (line 334) | class TypeAlias(TopLevel):
function json_to_ir (line 347) | def json_to_ir(json: Any) -> list[TopLevelUnion]:
function _structure (line 364) | def _structure(*types: Any) -> Callable[[Callable[P, T]], Callable[P, T]]:
function structure_description (line 374) | def structure_description(x: Any, _: Any) -> Description | bool:
function get_type_literal (line 382) | def get_type_literal(t: type[DescriptionText]) -> str:
function structure_description_item (line 396) | def structure_description_item(x: Any, _: Any) -> DescriptionItem:
function structure_type (line 402) | def structure_type(x: Any, _: Any) -> Type:
function structure_str_or_xref (line 409) | def structure_str_or_xref(x: Any, _: Any) -> Type:
function structure_str_or_nodefault (line 416) | def structure_str_or_nodefault(x: Any, _: Any) -> str | _NoDefault:
FILE: sphinx_js/js/cli.ts
class ExitError (line 25) | class ExitError extends Error {
method constructor (line 27) | constructor(code: number) {
function bootstrapAppTypedoc0_25 (line 33) | async function bootstrapAppTypedoc0_25(args: string[]): Promise<Applicat...
function makeApp (line 48) | async function makeApp(args: string[]): Promise<Application> {
function loadConfig (line 61) | async function loadConfig(
function typedocConvert (line 71) | async function typedocConvert(app: Application): Promise<ProjectReflecti...
function run (line 94) | async function run(
FILE: sphinx_js/js/convertTopLevel.ts
function parseFilePath (line 41) | function parseFilePath(path: string, base_dir: string): string[] {
function isAnonymousTypeLiteral (line 82) | function isAnonymousTypeLiteral(
class PathComputer (line 94) | class PathComputer implements ReflectionVisitor {
method constructor (line 110) | constructor(
method fixSymbolName (line 131) | static fixSymbolName(refl: DeclarationReflection | SignatureReflection) {
method computePath (line 144) | static computePath(
method setPath (line 181) | setPath(refl: DeclarationReflection | SignatureReflection): Pathname {
method project (line 201) | project(project: ProjectReflection) {
method declaration (line 216) | declaration(refl: DeclarationReflection) {
method signature (line 243) | signature(refl: SignatureReflection) {
function renderCommentContent (line 255) | function renderCommentContent(content: CommentDisplayPart[]): Description {
function renderCommentSummary (line 267) | function renderCommentSummary(c: Comment | undefined): Description {
function getCommentBlockTags (line 277) | function getCommentBlockTags(c: Comment | undefined): {
type ConvertResult (line 309) | type ConvertResult = [
type ParamReflSubset (line 320) | type ParamReflSubset = Pick<
class Converter (line 334) | class Converter {
method constructor (line 348) | constructor(
method convertType (line 365) | convertType(type: SomeType, context: TypeContext = TypeContext.none): ...
method referenceToXRef (line 375) | referenceToXRef(type: ReferenceType): Type {
method computePaths (line 384) | computePaths() {
method convertAll (line 398) | convertAll(): TopLevelIR[] {
method toIr (line 422) | toIr(object: DeclarationReflection | SignatureReflection): ConvertResu...
method convertFunction (line 456) | convertFunction(func: DeclarationReflection): ConvertResult {
method convertMethod (line 459) | convertMethod(func: DeclarationReflection): ConvertResult {
method convertConstructor (line 462) | convertConstructor(func: DeclarationReflection): ConvertResult {
method convertVariable (line 465) | convertVariable(v: DeclarationReflection): ConvertResult {
method relatedTypes (line 489) | relatedTypes(
method convertClass (line 504) | convertClass(cls: DeclarationReflection): ConvertResult {
method convertInterface (line 519) | convertInterface(cls: DeclarationReflection): ConvertResult {
method convertProperty (line 531) | convertProperty(prop: DeclarationReflection): ConvertResult {
method convertAccessor (line 582) | convertAccessor(prop: DeclarationReflection): ConvertResult {
method convertClassChild (line 610) | convertClassChild(child: DeclarationReflection): IRFunction | Attribute {
method constructorAndMembers (line 639) | constructorAndMembers(
method memberProps (line 662) | memberProps(refl: DeclarationReflection): Member {
method topLevelProperties (line 674) | topLevelProperties(
method _destructureParam (line 729) | _destructureParam(param: ParameterReflection): ParamReflSubset[] {
method _destructureParams (line 763) | _destructureParams(sig: SignatureReflection): ParamReflSubset[] {
method paramToIR (line 793) | paramToIR(param: ParamReflSubset): Param {
method functionToIR (line 821) | functionToIR(func: DeclarationReflection): IRFunction {
method typeParamsToIR (line 875) | typeParamsToIR(
method typeParamToIR (line 881) | typeParamToIR(typeParam: TypeParameterReflection): TypeParam {
method convertTypeAlias (line 892) | convertTypeAlias(ty: DeclarationReflection): ConvertResult {
FILE: sphinx_js/js/convertType.ts
class TypeConverter (line 44) | class TypeConverter implements TypeVisitor<Type> {
method constructor (line 53) | constructor(
method addTypeArguments (line 69) | addTypeArguments(
method convert (line 89) | convert(type: SomeType, context: TypeContext): Type {
method conditional (line 98) | conditional(type: ConditionalType): Type {
method indexedAccess (line 109) | indexedAccess(type: IndexedAccessType): Type {
method inferred (line 117) | inferred(type: InferredType): Type {
method intersection (line 126) | intersection(type: IntersectionType): Type {
method intrinsic (line 135) | intrinsic(type: IntrinsicType): Type {
method literal (line 138) | literal(type: LiteralType): Type {
method mapped (line 144) | mapped(type: MappedType): Type {
method optional (line 182) | optional(type: OptionalType): Type {
method predicate (line 188) | predicate(type: PredicateType): Type {
method query (line 198) | query(type: QueryType): Type {
method convertPrivateReferenceToReflection (line 208) | convertPrivateReferenceToReflection(type: ReferenceType): Type | undef...
method convertReferenceToXRef (line 258) | convertReferenceToXRef(type: ReferenceType): Type {
method reference (line 307) | reference(type: ReferenceType): Type {
method reflection (line 322) | reflection(type: ReflectionType): Type {
method rest (line 337) | rest(type: RestType): Type {
method templateLiteral (line 340) | templateLiteral(type: TemplateLiteralType): Type {
method tuple (line 355) | tuple(type: TupleType): Type {
method namedTupleMember (line 366) | namedTupleMember(type: NamedTupleMember): Type {
method typeOperator (line 371) | typeOperator(type: TypeOperatorType): Type {
method union (line 378) | union(type: UnionType): Type {
method unknown (line 387) | unknown(type: UnknownType): Type {
method array (line 392) | array(t: ArrayType): Type {
method convertSignature (line 398) | convertSignature(sig: SignatureReflection): Type {
method convertTypeLiteral (line 417) | convertTypeLiteral(lit: DeclarationReflection): Type {
function convertType (line 462) | function convertType(
function convertTypeLiteral (line 476) | function convertTypeLiteral(
function referenceToXRef (line 489) | function referenceToXRef(
FILE: sphinx_js/js/importHooks.mjs
function tryResolve (line 1) | async function tryResolve(specifier, context, nextResolve) {
function resolve (line 14) | async function resolve(specifier, context, nextResolve) {
FILE: sphinx_js/js/ir.ts
type TypeXRefIntrinsic (line 4) | type TypeXRefIntrinsic = {
function intrinsicType (line 9) | function intrinsicType(name: string): TypeXRefIntrinsic {
type TypeXRefInternal (line 16) | type TypeXRefInternal = {
type TypeXRefExternal (line 22) | type TypeXRefExternal = {
type TypeXRef (line 30) | type TypeXRef = TypeXRefExternal | TypeXRefInternal | TypeXRefIntrinsic;
type Type (line 31) | type Type = (string | TypeXRef)[];
type DescriptionName (line 33) | type DescriptionName = {
type DescriptionText (line 38) | type DescriptionText = {
type DescriptionCode (line 43) | type DescriptionCode = {
type DescriptionItem (line 48) | type DescriptionItem =
type Description (line 52) | type Description = DescriptionItem[];
type Pathname (line 54) | type Pathname = string[];
type NoDefault (line 56) | type NoDefault = { _no_default: true };
constant NO_DEFAULT (line 57) | const NO_DEFAULT: NoDefault = { _no_default: true };
type Member (line 59) | type Member = {
type TypeParam (line 66) | type TypeParam = {
type Param (line 72) | type Param = {
type Return (line 81) | type Return = {
type Module (line 86) | type Module = {
type TopLevel (line 96) | type TopLevel = {
type Attribute (line 113) | type Attribute = TopLevel &
type IRFunction (line 120) | type IRFunction = TopLevel &
type _MembersAndSupers (line 130) | type _MembersAndSupers = {
type Interface (line 135) | type Interface = TopLevel &
type Class (line 141) | type Class = TopLevel &
type TypeAlias (line 150) | type TypeAlias = TopLevel & {
type TopLevelIR (line 156) | type TopLevelIR = Attribute | IRFunction | Class | Interface | TypeAlias;
FILE: sphinx_js/js/main.ts
function main (line 4) | async function main() {
FILE: sphinx_js/js/redirectPrivateAliases.ts
type SymbolToTypeKey (line 42) | type SymbolToTypeKey = `${string}:${number}` | `${string}:${string}`;
type SymbolToType (line 43) | type SymbolToType = Map<SymbolToTypeKey, SomeType>;
type ReadonlySymbolToType (line 44) | type ReadonlySymbolToType = ReadonlyMap<SymbolToTypeKey, SomeType>;
function getOwningModule (line 49) | function getOwningModule(context: Context): Reflection {
function redirectPrivateTypes (line 62) | function redirectPrivateTypes(app: Application): ReadonlySymbolToType {
FILE: sphinx_js/js/sphinxJsConfig.ts
type SphinxJsConfig (line 9) | type SphinxJsConfig = {
FILE: sphinx_js/js/typedocPatches.ts
type TypeDocOptionMap (line 3) | interface TypeDocOptionMap {
type Application (line 6) | interface Application {
FILE: sphinx_js/js/typedocPlugin.ts
function load (line 8) | async function load(app: any): Promise<void> {
FILE: sphinx_js/jsdoc.py
class JsDocCode (line 43) | class JsDocCode(TypedDict, total=False):
class Meta (line 47) | class Meta(TypedDict, total=False):
class JsdocType (line 54) | class JsdocType(TypedDict, total=False):
class Doclet (line 58) | class Doclet(TypedDict, total=False):
class Analyzer (line 79) | class Analyzer:
method __init__ (line 85) | def __init__(self, json: list[Doclet], base_dir: str):
method from_disk (line 131) | def from_disk(
method get_object (line 143) | def get_object(
method _doclet_as_class (line 172) | def _doclet_as_class(self, doclet: Doclet, full_path: Sequence[str]) -...
method _doclet_as_function (line 203) | def _doclet_as_function(self, doclet: Doclet, full_path: Sequence[str]...
method _doclet_as_attribute (line 218) | def _doclet_as_attribute(
function is_private (line 233) | def is_private(doclet: Doclet) -> bool:
function is_static (line 237) | def is_static(doclet: Doclet) -> bool:
function full_path_segments (line 241) | def full_path_segments(
function jsdoc_output (line 273) | def jsdoc_output(
function format_default_according_to_type_hints (line 313) | def format_default_according_to_type_hints(
function description (line 355) | def description(obj: Doclet) -> str:
function get_type (line 359) | def get_type(props: Doclet) -> str | None:
function top_level_properties (line 367) | def top_level_properties(
function properties_to_ir (line 396) | def properties_to_ir(properties: list[Doclet]) -> list[Attribute]:
function first_type_is_string (line 423) | def first_type_is_string(type: JsdocType) -> bool:
function params_to_ir (line 428) | def params_to_ir(doclet: Doclet) -> list[Param]:
function exceptions_to_ir (line 484) | def exceptions_to_ir(exceptions: list[Doclet]) -> list[Exc]:
function returns_to_ir (line 489) | def returns_to_ir(returns: list[Doclet]) -> list[Return]:
FILE: sphinx_js/parsers.py
class PathVisitor (line 30) | class PathVisitor(NodeVisitor): # type:ignore[type-arg]
method visit_path_and_formal_params (line 33) | def visit_path_and_formal_params(self, node, children):
method visit_name (line 36) | def visit_name(self, node, children):
method visit_sep (line 41) | def visit_sep(self, node, children):
method visit_path (line 44) | def visit_path(self, node, children):
method visit_name_and_sep (line 51) | def visit_name_and_sep(self, node, children):
method visit_formal_params (line 55) | def visit_formal_params(self, node, children):
method visit_relative_dir (line 58) | def visit_relative_dir(self, node, children):
method visit_cur_dir (line 62) | def visit_cur_dir(self, node, children):
method visit_middle_segments (line 65) | def visit_middle_segments(self, node, children):
method generic_visit (line 68) | def generic_visit(self, node, visited_children):
function _backslash_unescape (line 74) | def _backslash_unescape(str):
FILE: sphinx_js/renderers.py
function new_document_from_parent (line 54) | def new_document_from_parent(
function sort_attributes_first_then_by_path (line 67) | def sort_attributes_first_then_by_path(obj: TopLevel) -> Any:
function _members_to_include_inner (line 80) | def _members_to_include_inner(
function members_to_include (line 120) | def members_to_include(
function unwrapped (line 134) | def unwrapped(text: str) -> str:
function render_description (line 139) | def render_description(description: ir.Description) -> str:
class HasDepPath (line 186) | class HasDepPath(Protocol):
class Renderer (line 190) | class Renderer:
method _parse_path (line 200) | def _parse_path(self, arg: str) -> None:
method __init__ (line 210) | def __init__(
method from_directive (line 231) | def from_directive(cls: type[R], directive: Directive, app: Sphinx) -> R:
method _set_type_xref_formatter (line 250) | def _set_type_xref_formatter(
method get_object (line 262) | def get_object(self) -> HasDepPath:
method dependencies (line 265) | def dependencies(self) -> set[str]:
method rst_nodes (line 279) | def rst_nodes(self) -> list[Node]:
class JsRenderer (line 283) | class JsRenderer(Renderer):
method _template_vars (line 297) | def _template_vars(self, name: str, obj: TopLevel) -> dict[str, Any]:
method lookup_object (line 300) | def lookup_object(
method get_object (line 323) | def get_object(self) -> TopLevel:
method rst_nodes (line 327) | def rst_nodes(self) -> list[Node]:
method rst_for (line 350) | def rst_for(self, obj: TopLevel) -> str:
method rst (line 366) | def rst(
method _type_params (line 390) | def _type_params(self, obj: Function | Class | TypeAlias | Interface) ...
method _formal_params (line 395) | def _formal_params(self, obj: Function) -> str:
method render_type (line 430) | def render_type(self, type: Type, escape: bool = False, bold: bool = T...
method render_xref (line 466) | def render_xref(self, s: TypeXRef, escape: bool = False) -> str:
method _return_formatter (line 486) | def _return_formatter(self, return_: Return) -> tuple[list[str], str]:
method _type_param_formatter (line 495) | def _type_param_formatter(self, tparam: TypeParam) -> tuple[list[str],...
method _param_formatter (line 503) | def _param_formatter(self, param: Param) -> tuple[list[str], str] | None:
method _param_type_formatter (line 514) | def _param_type_formatter(self, param: Param) -> tuple[list[str], str]...
method _exception_formatter (line 522) | def _exception_formatter(self, exception: Exc) -> tuple[list[str], str]:
method _fields (line 530) | def _fields(self, obj: TopLevel) -> Iterator[tuple[list[str], str]]:
class AutoFunctionRenderer (line 563) | class AutoFunctionRenderer(JsRenderer):
method _template_vars (line 567) | def _template_vars(self, name: str, obj: Function) -> dict[str, Any]: ...
class AutoClassRenderer (line 587) | class AutoClassRenderer(JsRenderer):
method _template_vars (line 591) | def _template_vars(self, name: str, obj: Class | Interface) -> dict[st...
method _members_of (line 651) | def _members_of(
class AutoAttributeRenderer (line 675) | class AutoAttributeRenderer(JsRenderer):
method _template_vars (line 679) | def _template_vars(self, name: str, obj: Attribute | TypeAlias) -> dic...
class AutoModuleRenderer (line 710) | class AutoModuleRenderer(JsRenderer):
method _parse_path (line 711) | def _parse_path(self, arg: str) -> None:
method get_object (line 719) | def get_object(self) -> Module: # type:ignore[override]
method rst_for_group (line 724) | def rst_for_group(self, objects: Iterable[TopLevel]) -> list[str]:
method rst (line 731) | def rst( # type:ignore[override]
class AutoSummaryRenderer (line 744) | class AutoSummaryRenderer(Renderer):
method _parse_path (line 745) | def _parse_path(self, arg: str) -> None:
method get_object (line 753) | def get_object(self) -> Module:
method rst_nodes (line 758) | def rst_nodes(self) -> list[Node]:
method format_heading (line 775) | def format_heading(self, text: str) -> Node:
method extract_summary (line 785) | def extract_summary(self, descr: str) -> str:
method get_sig (line 805) | def get_sig(self, obj: TopLevel) -> str:
method get_summary_row (line 814) | def get_summary_row(self, pkgname: str, obj: TopLevel) -> tuple[str, s...
method get_summary_table (line 832) | def get_summary_table(
method format_table (line 846) | def format_table(self, items: list[tuple[str, str, str]]) -> list[Node]:
FILE: sphinx_js/suffix_tree.py
class _Tree (line 13) | class _Tree(TypedDict, total=False):
class SuffixTree (line 18) | class SuffixTree(Generic[T]):
method __init__ (line 24) | def __init__(self) -> None:
method add (line 35) | def add(self, unambiguous_segments: Sequence[str], value: T) -> None:
method add_many (line 51) | def add_many(
method get_with_path (line 72) | def get_with_path(self, segments: Sequence[str]) -> tuple[T, Sequence[...
method get (line 122) | def get(self, segments: Sequence[str]) -> T:
class SuffixError (line 126) | class SuffixError(Exception):
method __init__ (line 127) | def __init__(self, segments: Sequence[str]):
method __str__ (line 132) | def __str__(self) -> str:
class PathTaken (line 136) | class PathTaken(SuffixError):
class PathsTaken (line 142) | class PathsTaken(Exception):
method __init__ (line 149) | def __init__(self, conflicts: list[Sequence[str]]) -> None:
method __str__ (line 155) | def __str__(self) -> str:
class SuffixNotFound (line 166) | class SuffixNotFound(SuffixError):
class SuffixAmbiguous (line 172) | class SuffixAmbiguous(SuffixError):
method __init__ (line 175) | def __init__(
method __str__ (line 185) | def __str__(self) -> str:
FILE: sphinx_js/typedoc.py
function typedoc_version_info (line 29) | def typedoc_version_info(typedoc: str) -> tuple[tuple[int, ...], tuple[i...
function version_to_str (line 46) | def version_to_str(t: Sequence[int]) -> str:
function typedoc_output (line 50) | def typedoc_output(
class Analyzer (line 109) | class Analyzer:
method __init__ (line 114) | def __init__(
method get_object (line 125) | def get_object(
method from_disk (line 137) | def from_disk(
method _get_toplevel_objects (line 150) | def _get_toplevel_objects(
method _create_modules (line 159) | def _create_modules(self, ir_objects: Sequence[ir.TopLevel]) -> Iterab...
FILE: tests/conftest.py
function rootdir (line 36) | def rootdir():
FILE: tests/roots/test-incremental_js/a.js
class ClassA (line 4) | class ClassA {
method noUseOfThis (line 8) | static noUseOfThis() {}
method methodA (line 13) | methodA() {}
FILE: tests/roots/test-incremental_js/inner/b.js
class ClassB (line 4) | class ClassB {
method noUseOfThis (line 8) | static noUseOfThis() {}
method methodB (line 13) | methodB() {}
FILE: tests/roots/test-incremental_ts/a.ts
class ClassA (line 1) | class ClassA {
method constructor (line 2) | constructor() {}
method methodA (line 7) | methodA() {}
FILE: tests/roots/test-incremental_ts/inner/b.ts
class ClassB (line 1) | class ClassB {
method constructor (line 2) | constructor() {}
method methodB (line 7) | methodB() {}
FILE: tests/test.ts
function joinType (line 7) | function joinType(t: Type): string {
function resolveFile (line 11) | function resolveFile(path: string): string {
function getObject (line 33) | function getObject(name: string): TopLevelIR {
FILE: tests/test_build_js/source/code.js
function linkDensity (line 10) | function linkDensity(node) {
class ContainingClass (line 22) | class ContainingClass {
method constructor (line 28) | constructor(ho) {
method someMethod (line 39) | someMethod(hi) {}
method bar (line 44) | get bar() {
method bar (line 47) | set bar(baz) {
method anotherMethod (line 54) | anotherMethod() {}
method yetAnotherMethod (line 59) | yetAnotherMethod() {}
method secret (line 65) | secret() {}
class ClosedClass (line 70) | class ClosedClass {
method publical (line 74) | publical() {}
method publical2 (line 79) | publical2() {}
method publical3 (line 84) | publical3() {}
class NonAlphabetical (line 88) | class NonAlphabetical {
method z (line 90) | z() {}
method a (line 93) | a() {}
function shadow (line 103) | function shadow() {}
function exampleTag (line 123) | function exampleTag() {}
class ExampleClass (line 132) | class ExampleClass {}
function destructuredParams (line 149) | function destructuredParams(p1, { foo, bar }) {}
function injection (line 156) | function injection() {}
function defaultsDocumentedInDoclet (line 166) | function defaultsDocumentedInDoclet(func, str, strNum, strBool, num, nil...
function defaultsDocumentedInCode (line 174) | function defaultsDocumentedInCode(
function variadicParameter (line 186) | function variadicParameter(a, ...args) {}
function deprecatedFunction (line 189) | function deprecatedFunction() {}
function deprecatedExplanatoryFunction (line 191) | function deprecatedExplanatoryFunction() {}
class DeprecatedClass (line 199) | class DeprecatedClass {}
class DeprecatedExplanatoryClass (line 201) | class DeprecatedExplanatoryClass {}
function seeFunction (line 208) | function seeFunction() {}
class SeeClass (line 220) | class SeeClass {}
function union (line 225) | function union(fnodeA) {}
function longDescriptions (line 237) | function longDescriptions(a, b) {}
class SimpleClass (line 242) | class SimpleClass {
method staticMethod (line 248) | static staticMethod() {}
method nonStaticMethod (line 255) | nonStaticMethod() {}
FILE: tests/test_build_js/source/more_code.js
function shadow (line 4) | function shadow() {}
FILE: tests/test_build_js/test_build_js.py
class Tests (line 12) | class Tests(SphinxBuildTestCase):
method test_autofunction_minimal (line 23) | def test_autofunction_minimal(self):
method test_autofunction_explicit (line 30) | def test_autofunction_explicit(self):
method test_autofunction_short (line 39) | def test_autofunction_short(self):
method test_autofunction_long (line 43) | def test_autofunction_long(self):
method test_autofunction_typedef (line 50) | def test_autofunction_typedef(self):
method test_autofunction_callback (line 57) | def test_autofunction_callback(self):
method test_autofunction_example (line 64) | def test_autofunction_example(self):
method test_autofunction_destructured_params (line 75) | def test_autofunction_destructured_params(self):
method test_autofunction_defaults_in_doclet (line 88) | def test_autofunction_defaults_in_doclet(self):
method test_autofunction_defaults_in_code (line 100) | def test_autofunction_defaults_in_code(self):
method test_autofunction_variadic (line 108) | def test_autofunction_variadic(self):
method test_autofunction_deprecated (line 115) | def test_autofunction_deprecated(self):
method test_autofunction_see (line 127) | def test_autofunction_see(self):
method test_autofunction_static (line 138) | def test_autofunction_static(self):
method test_autoclass (line 162) | def test_autoclass(self):
method test_autoclass_members (line 169) | def test_autoclass_members(self):
method test_autoclass_members_list (line 208) | def test_autoclass_members_list(self):
method test_autoclass_members_list_star (line 216) | def test_autoclass_members_list_star(self):
method test_autoclass_alphabetical (line 252) | def test_autoclass_alphabetical(self):
method test_autoclass_private_members (line 259) | def test_autoclass_private_members(self):
method test_autoclass_exclude_members (line 265) | def test_autoclass_exclude_members(self):
method test_autoclass_example (line 273) | def test_autoclass_example(self):
method test_autoclass_deprecated (line 284) | def test_autoclass_deprecated(self):
method test_autoclass_see (line 296) | def test_autoclass_see(self):
method test_autoattribute (line 307) | def test_autoattribute(self):
method test_autoattribute_example (line 311) | def test_autoattribute_example(self):
method test_autoattribute_deprecated (line 322) | def test_autoattribute_deprecated(self):
method test_autoattribute_see (line 334) | def test_autoattribute_see(self):
method test_getter_setter (line 345) | def test_getter_setter(self):
method test_no_shadowing (line 352) | def test_no_shadowing(self):
method test_restructuredtext_injection (line 359) | def test_restructuredtext_injection(self):
method test_union_types (line 377) | def test_union_types(self):
method test_field_list_unwrapping (line 390) | def test_field_list_unwrapping(self):
FILE: tests/test_build_ts/source/class.ts
class ClassDefinition (line 4) | class ClassDefinition {
method constructor (line 11) | constructor(simple: number) {}
method method1 (line 17) | method1(simple: number): void {}
method anotherMethod (line 22) | anotherMethod() {}
type Interface (line 25) | interface Interface {}
method constructor (line 32) | constructor() {
type InterfaceWithSupers (line 37) | interface InterfaceWithSupers extends Interface {}
class ExportedClass (line 39) | class ExportedClass {
method constructor (line 40) | constructor() {}
class ConstructorlessClass (line 43) | class ConstructorlessClass {}
type OptionalThings (line 45) | interface OptionalThings {
function blah (line 65) | function blah(a: OptionalThings): ConstructorlessClass {
function thunk (line 69) | function thunk(b: typeof blah) {}
function selfReferential (line 75) | function selfReferential(b: typeof selfReferential) {}
function deprecatedFunction1 (line 80) | function deprecatedFunction1() {}
function deprecatedFunction2 (line 85) | function deprecatedFunction2() {}
function exampleFunction (line 94) | function exampleFunction() {}
function asyncFunction (line 96) | async function asyncFunction() {}
class Iterable (line 98) | class Iterable {
method [Symbol.iterator] (line 99) | *[Symbol.iterator](): Iterator<number, string, undefined> {
function predicate (line 107) | function predicate(c): c is ConstructorlessClass {
function weirdCodeInDescription (line 115) | function weirdCodeInDescription() {}
function spinxLinkInDescription (line 120) | function spinxLinkInDescription() {}
class GetSetDocs (line 122) | class GetSetDocs {
method a (line 129) | get a() {
method b (line 136) | set b(x: number) {}
class Base (line 139) | class Base {
method f (line 141) | f() {}
method a (line 143) | get a() {
class Extension (line 148) | class Extension extends Base {
method g (line 150) | g() {}
FILE: tests/test_build_ts/source/docs/conf.py
function ts_type_xref_formatter (line 17) | def ts_type_xref_formatter(config, xref: TypeXRef) -> str:
FILE: tests/test_build_ts/source/module.ts
function f (line 9) | async function f() {}
function z (line 11) | function z(a: number, b: typeof q): number {
class A (line 18) | class A {
method f (line 19) | async f() {}
method g (line 23) | g(a: number): number {
method [Symbol.iterator] (line 21) | [Symbol.iterator]() {}
class Z (line 36) | class Z<T extends A> {
method constructor (line 38) | constructor(a: number, b: number) {}
method z (line 40) | z() {}
type I (line 53) | interface I {}
type TestTypeAlias (line 64) | type TestTypeAlias<T extends A> = { a: T };
type TestTypeAlias2 (line 65) | type TestTypeAlias2 = { a: number };
type TestTypeAlias3 (line 71) | type TestTypeAlias3 = { a: number };
function functionWithTypeParam (line 86) | function functionWithTypeParam<T extends A>(z: Z<T>): T {
FILE: tests/test_build_ts/test_build_ts.py
class TestTextBuilder (line 8) | class TestTextBuilder(SphinxBuildTestCase):
method test_autoclass_constructor (line 11) | def test_autoclass_constructor(self):
method test_autoclass_order (line 20) | def test_autoclass_order(self):
method test_autoclass_star_order (line 30) | def test_autoclass_star_order(self):
method test_abstract_extends_and_implements (line 40) | def test_abstract_extends_and_implements(self):
method test_exported_from (line 68) | def test_exported_from(self):
method test_constructorless_class (line 81) | def test_constructorless_class(self):
method test_optional_members (line 88) | def test_optional_members(self):
method test_deprecated (line 112) | def test_deprecated(self):
method test_example (line 132) | def test_example(self):
method test_async (line 150) | def test_async(self):
method test_symbol (line 163) | def test_symbol(self):
method test_predicate (line 178) | def test_predicate(self):
method test_get_set (line 194) | def test_get_set(self):
method test_inherited_docs (line 226) | def test_inherited_docs(self):
method test_automodule (line 258) | def test_automodule(self):
class TestHtmlBuilder (line 393) | class TestHtmlBuilder(SphinxBuildTestCase):
method test_extends_links (line 399) | def test_extends_links(self):
method test_implements_links (line 405) | def test_implements_links(self):
method test_extends_type_param_links (line 411) | def test_extends_type_param_links(self):
method test_xrefs (line 426) | def test_xrefs(self):
method test_sphinx_link_in_description (line 451) | def test_sphinx_link_in_description(self):
method test_sphinx_js_type_class (line 459) | def test_sphinx_js_type_class(self):
method test_autosummary (line 465) | def test_autosummary(self):
FILE: tests/test_build_xref_none/source/docs/conf.py
function ts_type_xref_formatter (line 10) | def ts_type_xref_formatter(config, xref):
FILE: tests/test_build_xref_none/test_build_xref_none.py
function test_build_fails_with_invalid_role (line 17) | def test_build_fails_with_invalid_role(tmp_path: Path):
FILE: tests/test_common_mark/source/code.js
function foo (line 4) | function foo() {}
FILE: tests/test_common_mark/source/docs/conf.py
function setup (line 10) | def setup(app):
FILE: tests/test_common_mark/test_common_mark.py
class Tests (line 4) | class Tests(SphinxBuildTestCase):
method test_build_success (line 5) | def test_build_success(self):
FILE: tests/test_dot_dot_paths/source/code.js
function bar (line 4) | function bar(node) {}
FILE: tests/test_dot_dot_paths/test_dot_dot_paths.py
class Tests (line 4) | class Tests(SphinxBuildTestCase):
method test_dot_dot (line 5) | def test_dot_dot(self):
FILE: tests/test_incremental.py
function build (line 15) | def build(app):
function do_test (line 49) | def do_test(app, extension="js"):
function test_incremental_js (line 89) | def test_incremental_js(make_app, app_params):
function test_incremental_ts (line 98) | def test_incremental_ts(make_app, app_params):
FILE: tests/test_init.py
function test_relative_path_root (line 7) | def test_relative_path_root():
FILE: tests/test_ir.py
function test_default (line 23) | def test_default():
function test_missing_default (line 30) | def test_missing_default():
function attr_with (line 68) | def attr_with(**kwargs):
function func_with (line 86) | def func_with(**kwargs):
function test_ir_serialization (line 130) | def test_ir_serialization(x):
FILE: tests/test_jsdoc_analysis/source/class.js
class Foo (line 8) | class Foo {
method constructor (line 15) | constructor(ho) {}
method bar (line 20) | get bar() {
method bar (line 23) | set bar(baz) {
method secret (line 32) | secret() {}
FILE: tests/test_jsdoc_analysis/source/function.js
function foo (line 17) | function foo(bar, baz = 8) {}
FILE: tests/test_jsdoc_analysis/test_jsdoc.py
function test_doclet_full_path (line 14) | def test_doclet_full_path():
class TestFunction (line 33) | class TestFunction(JsDocTestCase):
method test_top_level_and_function (line 36) | def test_top_level_and_function(self):
class TestClass (line 98) | class TestClass(JsDocTestCase):
method test_class (line 101) | def test_class(self):
FILE: tests/test_parsers.py
function test_escapes (line 4) | def test_escapes():
function test_relative_dirs (line 12) | def test_relative_dirs():
FILE: tests/test_paths.py
function clear_node_modules_env (line 12) | def clear_node_modules_env(monkeypatch):
function global_install (line 17) | def global_install(tmp_path_factory, monkeypatch):
function no_local_install (line 27) | def no_local_install(tmp_path_factory):
function local_install (line 37) | def local_install(no_local_install):
function env_install (line 46) | def env_install(monkeypatch):
function test_global (line 52) | def test_global(global_install, no_local_install):
function test_node_modules1 (line 60) | def test_node_modules1(global_install, local_install):
function test_node_modules2 (line 66) | def test_node_modules2(local_install):
function test_env1 (line 72) | def test_env1(env_install):
function test_env2 (line 77) | def test_env2(env_install, local_install, global_install):
function test_err (line 85) | def test_err():
function test_global_install (line 94) | def test_global_install(tmp_path_factory, monkeypatch):
FILE: tests/test_renderers.py
function setindent (line 31) | def setindent(txt):
function test_render_description (line 35) | def test_render_description():
function ts_xref_formatter (line 65) | def ts_xref_formatter(config, xref):
function make_renderer (line 73) | def make_renderer(cls):
function function_renderer (line 88) | def function_renderer():
function attribute_renderer (line 99) | def attribute_renderer():
function auto_module_renderer (line 104) | def auto_module_renderer():
function function_render (line 118) | def function_render(function_renderer) -> Any:
function attribute_render (line 133) | def attribute_render(attribute_renderer) -> Any:
function type_alias_render (line 145) | def type_alias_render(attribute_renderer) -> Any:
function auto_module_render (line 157) | def auto_module_render(auto_module_renderer) -> Any:
function make_class (line 222) | def make_class(**args):
function make_interface (line 226) | def make_interface(**args):
function make_function (line 230) | def make_function(**args):
function make_attribute (line 234) | def make_attribute(**args):
function make_type_alias (line 238) | def make_type_alias(**args):
function make_module (line 242) | def make_module(**args):
function test_func_render_simple (line 249) | def test_func_render_simple(function_render):
function test_func_render_shortnames (line 253) | def test_func_render_shortnames(function_render):
function test_func_render_flags (line 261) | def test_func_render_flags(function_render):
function test_func_render_description (line 275) | def test_func_render_description(function_render):
function test_func_render_params (line 285) | def test_func_render_params(function_render):
function test_func_render_returns (line 313) | def test_func_render_returns(function_render):
function test_func_render_type_params (line 329) | def test_func_render_type_params(function_render):
function test_render_xref (line 350) | def test_render_xref(function_renderer: AutoFunctionRenderer):
function test_func_render_param_type (line 390) | def test_func_render_param_type(function_render):
function test_func_render_param_options (line 423) | def test_func_render_param_options(function_render):
function test_func_render_param_exceptions (line 446) | def test_func_render_param_exceptions(function_render):
function test_func_render_callouts (line 460) | def test_func_render_callouts(function_render):
function test_all (line 485) | def test_all(function_render):
function test_examples (line 517) | def test_examples(function_render):
function test_type_alias (line 567) | def test_type_alias(type_alias_render):
function test_auto_module_render (line 601) | def test_auto_module_render(auto_module_render):
FILE: tests/test_suffix_tree.py
function test_things (line 6) | def test_things():
function test_full_path (line 23) | def test_full_path():
function test_terminal_insertion (line 33) | def test_terminal_insertion():
function test_ambiguous_even_if_full_path (line 42) | def test_ambiguous_even_if_full_path():
function test_ambiguous_paths_reported (line 52) | def test_ambiguous_paths_reported():
function test_value_ambiguity (line 64) | def test_value_ambiguity():
FILE: tests/test_testing.py
function test_dict_where (line 4) | def test_dict_where():
FILE: tests/test_typedoc_analysis/source/exports.ts
type Blah (line 1) | interface Blah {
FILE: tests/test_typedoc_analysis/source/nodes.ts
class Superclass (line 1) | class Superclass {
method method (line 2) | method() {}
type SuperInterface (line 5) | interface SuperInterface {}
type Interface (line 7) | interface Interface extends SuperInterface {}
type InterfaceWithMembers (line 9) | interface InterfaceWithMembers {
function func (line 29) | function func(a: number = 1, ...b: string[]): number {
class ClassWithProperties (line 33) | class ClassWithProperties {
method constructor (line 42) | constructor(a: number) {}
method gettable (line 44) | get gettable(): number {
method settable (line 48) | set settable(value: string) {}
class Indexable (line 51) | class Indexable {
type TestTypeAlias (line 62) | type TestTypeAlias<T> = 1 | 2 | T;
FILE: tests/test_typedoc_analysis/source/subdir/pathSegments.ts
function foo (line 4) | function foo(): void {
class Foo (line 15) | class Foo {
method constructor (line 34) | constructor(num: number) {
method someMethod (line 41) | someMethod(): void {}
method staticMethod (line 46) | static staticMethod(): void {}
method getter (line 51) | get getter(): number {
method setter (line 58) | set setter(n: number) {}
type Face (line 61) | interface Face {
FILE: tests/test_typedoc_analysis/source/types.ts
type Color (line 5) | enum Color {
type Interface (line 28) | interface Interface {
function interfacer (line 33) | function interfacer(a: Interface) {}
type FunctionInterface (line 35) | interface FunctionInterface {
function noThis (line 41) | function noThis(this: void) {
function overload (line 48) | function overload(x): any {}
type CertainNumbers (line 52) | type CertainNumbers = 1 | 2 | 4;
type FooHaver (line 59) | interface FooHaver {
type BarHaver (line 63) | interface BarHaver {
function aryIdentity (line 71) | function aryIdentity<T>(things: T[]): T[] {
class GenericNumber (line 76) | class GenericNumber<T> {
type Lengthwise (line 82) | interface Lengthwise {
function constrainedIdentity (line 89) | function constrainedIdentity<T extends Lengthwise>(arg: T): T {
function getProperty (line 97) | function getProperty<T, K extends keyof T>(obj: T, key: K) {
class A (line 101) | class A {}
function create1 (line 103) | function create1(c: { new (x: number): A }): A {
function create2 (line 107) | function create2<T>(c: { new (): T }): T {
class ParamClass (line 114) | class ParamClass<S extends number[]> {
method constructor (line 115) | constructor() {}
function objProps (line 133) | function objProps(
function codeInDescription (line 150) | function codeInDescription() {}
function destructureTest (line 160) | function destructureTest({ a, b }: { b: { c: string }; a: string }) {}
function destructureTest2 (line 168) | function destructureTest2({
function destructureTest3 (line 185) | function destructureTest3({ a, b }: { a: string; b: { c: string } }) {}
function destructureTest4 (line 190) | function destructureTest4(destructureThisPlease: {
function funcArg (line 200) | function funcArg(a: (b: number, c: number) => number) {}
function namedTupleArg (line 202) | function namedTupleArg(namedTuple: [key: string, value: any]) {}
type PrivateTypeAlias1 (line 207) | type PrivateTypeAlias1 = { a: number; b: string };
type PrivateTypeAlias2 (line 213) | type PrivateTypeAlias2 = { a: number; b: string };
class HasHiddenTypeMember (line 224) | class HasHiddenTypeMember {
type ConditionalType (line 236) | type ConditionalType<T> = T extends A ? 1 : 2;
type InferredType (line 238) | type InferredType<T> = T extends Promise<infer S> ? S : T;
type keys (line 240) | type keys = "A" | "B" | "C";
type MappedType1 (line 242) | type MappedType1 = { [property in keys]: number };
type MappedType2 (line 243) | type MappedType2 = { -readonly [property in keys]?: number };
type MappedType3 (line 244) | type MappedType3 = { readonly [property in keys]-?: number };
type TemplateLiteral (line 246) | type TemplateLiteral = `${number}: ${string}`;
type OptionalType (line 248) | type OptionalType = [number?];
type CustomTags (line 256) | type CustomTags = {};
FILE: tests/test_typedoc_analysis/test_typedoc_analysis.py
function join_type (line 28) | def join_type(t: Type) -> str:
function join_description (line 36) | def join_description(t: Description) -> str:
class TestPathSegments (line 44) | class TestPathSegments(TypeDocTestCase):
method commented_object (line 49) | def commented_object(self, comment, **kwargs):
method commented_object_path (line 54) | def commented_object_path(self, comment, **kwargs):
method test_class (line 61) | def test_class(self):
method test_instance_property (line 69) | def test_instance_property(self):
method test_static_property (line 78) | def test_static_property(self):
method test_interface_property (line 87) | def test_interface_property(self):
method test_weird_name (line 96) | def test_weird_name(self):
method test_getter (line 107) | def test_getter(self):
method test_setter (line 116) | def test_setter(self):
method test_method (line 125) | def test_method(self):
method test_static_method (line 134) | def test_static_method(self):
method test_constructor (line 146) | def test_constructor(self):
method test_function (line 160) | def test_function(self):
method test_relative_paths (line 171) | def test_relative_paths(self):
method test_namespaced_var (line 183) | def test_namespaced_var(self):
class TestConvertNode (line 194) | class TestConvertNode(TypeDocAnalyzerTestCase):
method test_class1 (line 200) | def test_class1(self):
method test_interface (line 244) | def test_interface(self):
method test_interface_function_member (line 257) | def test_interface_function_member(self):
method test_variable (line 265) | def test_variable(self):
method test_function (line 271) | def test_function(self):
method test_constructor (line 305) | def test_constructor(self):
method test_properties (line 316) | def test_properties(self):
method test_getter (line 353) | def test_getter(self):
method test_setter (line 360) | def test_setter(self):
method test_type_alias (line 367) | def test_type_alias(self):
class TestTypeName (line 375) | class TestTypeName(TypeDocAnalyzerTestCase):
method test_basic (line 380) | def test_basic(self):
method test_named_interface (line 403) | def test_named_interface(self):
method test_interface_readonly_member (line 411) | def test_interface_readonly_member(self):
method test_array (line 421) | def test_array(self):
method test_literal_types (line 432) | def test_literal_types(self):
method test_unions (line 443) | def test_unions(self):
method test_intersection (line 455) | def test_intersection(self):
method test_generic_function (line 464) | def test_generic_function(self):
method test_generic_member (line 472) | def test_generic_member(self):
method test_constrained_by_interface (line 484) | def test_constrained_by_interface(self):
method test_constrained_by_key (line 499) | def test_constrained_by_key(self):
method test_class_constrained (line 535) | def test_class_constrained(self):
method test_constrained_by_constructor (line 556) | def test_constrained_by_constructor(self):
method test_utility_types (line 566) | def test_utility_types(self):
method test_internal_symbol_reference (line 586) | def test_internal_symbol_reference(self):
method test_constrained_by_property (line 597) | def test_constrained_by_property(self):
method test_optional_property (line 612) | def test_optional_property(self):
method test_code_in_description (line 618) | def test_code_in_description(self):
method test_destructured (line 632) | def test_destructured(self):
method test_funcarg (line 664) | def test_funcarg(self):
method test_namedtuplearg (line 670) | def test_namedtuplearg(self):
method test_query (line 676) | def test_query(self):
method test_type_operator (line 681) | def test_type_operator(self):
method test_private_type_alias1 (line 686) | def test_private_type_alias1(self):
method test_private_type_alias2 (line 691) | def test_private_type_alias2(self):
method test_hidden_type_top_level (line 696) | def test_hidden_type_top_level(self):
method test_hidden_type_member (line 702) | def test_hidden_type_member(self):
method test_rest_type (line 710) | def test_rest_type(self):
method test_indexed_access_type (line 715) | def test_indexed_access_type(self):
method test_conditional_type (line 720) | def test_conditional_type(self):
method test_inferred_type (line 725) | def test_inferred_type(self):
method test_mapped_type (line 730) | def test_mapped_type(self):
method test_template_literal (line 741) | def test_template_literal(self):
method test_custom_tags (line 746) | def test_custom_tags(self):
FILE: tests/testing.py
class ThisDirTestCase (line 14) | class ThisDirTestCase:
method this_dir (line 19) | def this_dir(cls):
class SphinxBuildTestCase (line 26) | class SphinxBuildTestCase(ThisDirTestCase):
method setup_class (line 35) | def setup_class(cls):
method teardown_class (line 45) | def teardown_class(cls):
method _file_contents (line 48) | def _file_contents(self, filename):
method _file_contents_eq (line 56) | def _file_contents_eq(self, filename, expected_contents):
class JsDocTestCase (line 64) | class JsDocTestCase(ThisDirTestCase):
method setup_class (line 68) | def setup_class(cls):
class TypeDocTestCase (line 77) | class TypeDocTestCase(ThisDirTestCase):
method setup_class (line 81) | def setup_class(cls):
class TypeDocAnalyzerTestCase (line 98) | class TypeDocAnalyzerTestCase(TypeDocTestCase):
method setup_class (line 102) | def setup_class(cls):
function dict_where (line 112) | def dict_where(json, already_seen=None, **kwargs):
Condensed preview — 159 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (383K chars).
[
{
"path": ".editorconfig",
"chars": 223,
"preview": "# http://editorconfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\ninsert_"
},
{
"path": ".github/codecov.yml",
"chars": 100,
"preview": "comment: false\ncodecov:\n branch: main\n require_ci_to_pass: false\n notify:\n wait_for_ci: false\n"
},
{
"path": ".github/dependabot.yml",
"chars": 578,
"preview": "# Keep GitHub Actions up to date with GitHub's Dependabot...\n# https://docs.github.com/en/code-security/dependabot/worki"
},
{
"path": ".github/workflows/check_ts.yml",
"chars": 1346,
"preview": "name: check-ts\non:\n pull_request:\npermissions: write-all\n\njobs:\n ts:\n if: false\n runs-on: ubuntu-latest\n step"
},
{
"path": ".github/workflows/ci.yml",
"chars": 2964,
"preview": "---\nname: CI\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n\njobs:\n test:\n runs-on: ubun"
},
{
"path": ".github/workflows/release.yml",
"chars": 1940,
"preview": "name: CD\n\non:\n release:\n types: [published]\n workflow_dispatch:\n schedule:\n - cron: \"0 3 * * 1\"\n\nenv:\n FORCE_C"
},
{
"path": ".github/workflows/test_report.yml",
"chars": 1376,
"preview": "name: \"Test Report\"\non:\n workflow_run:\n workflows: [\"CI\"]\n types:\n - completed\njobs:\n report:\n strategy:"
},
{
"path": ".gitignore",
"chars": 260,
"preview": "/.eggs\n/.tox\n/.pytest_cache\n/build\n/dist\n/node_modules\n_build\nsphinx_js.egg-info/\n# Python 3\n*/__pycache__/*\n# Python 2."
},
{
"path": ".pre-commit-config.yaml",
"chars": 1525,
"preview": "default_language_version:\n python: \"3.10\"\nrepos:\n - repo: https://github.com/pre-commit/pre-commit-hooks\n rev: \"v4."
},
{
"path": "CHANGELOG.md",
"chars": 12492,
"preview": "## Changelog\n\n### 5.0.3: (March 30th, 2026)\n\n- Test Python 3.14 in CI (#302)\n- Fix compatibility with sphinx 9 (#301)\n- "
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 5132,
"preview": "# Code of Conduct\n\n## Conduct\n\nWe are committed to providing a friendly, safe and welcoming environment for all, regardl"
},
{
"path": "CONTRIBUTORS",
"chars": 685,
"preview": "sphinx-js was originally written and maintained by Erik Rose and various\ncontributors within and without the Mozilla Cor"
},
{
"path": "LICENSE",
"chars": 1075,
"preview": "MIT License\n\nCopyright (c) 2017 Mozilla Foundation\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "README.md",
"chars": 21233,
"preview": "# sphinx-js\n\n## Why\n\nWhen you write a JavaScript library, how do you explain it to people? If it's a\nsmall project in a "
},
{
"path": "biome.json",
"chars": 1038,
"preview": "{\n\t\"$schema\": \"https://biomejs.dev/schemas/2.1.1/schema.json\",\n\t\"vcs\": { \"enabled\": false, \"clientKind\": \"git\", \"useIgno"
},
{
"path": "noxfile.py",
"chars": 2910,
"preview": "from pathlib import Path\nfrom textwrap import dedent\n\nimport nox\nfrom nox.sessions import Session\n\nPROJECT_ROOT = Path(_"
},
{
"path": "pyproject.toml",
"chars": 2752,
"preview": "[build-system]\nrequires = [\"hatchling\", \"hatch-vcs\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"sphinx-js\"\ndes"
},
{
"path": "sphinx_js/__init__.py",
"chars": 4215,
"preview": "from os.path import join, normpath\nfrom pathlib import Path\nfrom textwrap import dedent\nfrom typing import Any\n\nfrom sph"
},
{
"path": "sphinx_js/analyzer_utils.py",
"chars": 3594,
"preview": "\"\"\"Conveniences shared among analyzers\"\"\"\n\nimport os\nimport shutil\nfrom collections.abc import Callable, Sequence\nfrom f"
},
{
"path": "sphinx_js/directives.py",
"chars": 17062,
"preview": "\"\"\"These are the actual Sphinx directives we provide, but they are skeletal.\n\nThe real meat is in their parallel rendere"
},
{
"path": "sphinx_js/ir.py",
"chars": 13249,
"preview": "\"\"\"Intermediate representation that JS and TypeScript are transformed to for\nuse by the rest of sphinx-js\n\nThis results "
},
{
"path": "sphinx_js/js/cli.ts",
"chars": 3221,
"preview": "import {\n Application,\n ArgumentsReader,\n TypeDocReader,\n PackageJsonReader,\n TSConfigReader,\n ProjectReflection,\n"
},
{
"path": "sphinx_js/js/convertTopLevel.ts",
"chars": 29155,
"preview": "import {\n Comment,\n CommentDisplayPart,\n DeclarationReflection,\n ParameterReflection,\n ProjectReflection,\n Referen"
},
{
"path": "sphinx_js/js/convertType.ts",
"chars": 14030,
"preview": "import {\n ArrayType,\n ConditionalType,\n DeclarationReflection,\n IndexedAccessType,\n InferredType,\n IntersectionTyp"
},
{
"path": "sphinx_js/js/importHooks.mjs",
"chars": 903,
"preview": "async function tryResolve(specifier, context, nextResolve) {\n try {\n return await nextResolve(specifier, context);\n "
},
{
"path": "sphinx_js/js/ir.ts",
"chars": 3081,
"preview": "// Define the types for our IR. Must match the cattrs+json serialization\n// format from ir.py\n\nexport type TypeXRefIntri"
},
{
"path": "sphinx_js/js/main.ts",
"chars": 694,
"preview": "import { writeFile } from \"fs/promises\";\nimport { ExitError, run } from \"./cli.ts\";\n\nasync function main() {\n const sta"
},
{
"path": "sphinx_js/js/package.json",
"chars": 176,
"preview": "{\n \"name\": \"sphinx_js\",\n \"type\": \"module\",\n \"dependencies\": {\n \"tsx\": \"^4.9.0\",\n \"typedoc\": \"^0.25.13\"\n },\n \""
},
{
"path": "sphinx_js/js/redirectPrivateAliases.ts",
"chars": 6341,
"preview": "/**\n * This is very heavily inspired by typedoc-plugin-missing-exports.\n *\n * The goal isn't to document the missing exp"
},
{
"path": "sphinx_js/js/registerImportHook.mjs",
"chars": 89,
"preview": "import { register } from \"node:module\";\n\nregister(\"./importHooks.mjs\", import.meta.url);\n"
},
{
"path": "sphinx_js/js/sphinxJsConfig.ts",
"chars": 460,
"preview": "import {\n Application,\n DeclarationReflection,\n ParameterReflection,\n ProjectReflection,\n} from \"typedoc\";\nimport { "
},
{
"path": "sphinx_js/js/tsconfig.json",
"chars": 297,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"NodeNext\",\n \"moduleResolution\": \"NodeNext\",\n \"noEm"
},
{
"path": "sphinx_js/js/typedocPatches.ts",
"chars": 247,
"preview": "/** Declare some extra stuff we monkeypatch on to typedoc */\ndeclare module \"typedoc\" {\n export interface TypeDocOption"
},
{
"path": "sphinx_js/js/typedocPlugin.ts",
"chars": 467,
"preview": "/**\n * Typedoc plugin which adds --sphinxJsConfig option\n */\n\n// TODO: we don't seem to resolve imports correctly in thi"
},
{
"path": "sphinx_js/jsdoc.py",
"chars": 17760,
"preview": "\"\"\"JavaScript analyzer\n\nAnalyzers run jsdoc or typedoc or whatever, squirrel away their output, and\nthen lazily constitu"
},
{
"path": "sphinx_js/parsers.py",
"chars": 2453,
"preview": "from re import sub\n\nfrom parsimonious import Grammar, NodeVisitor\n\npath_and_formal_params = Grammar(\n r\"\"\"\n path_a"
},
{
"path": "sphinx_js/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "sphinx_js/renderers.py",
"chars": 33460,
"preview": "import textwrap\nfrom collections.abc import Callable, Iterable, Iterator, Sequence\nfrom functools import partial\nfrom re"
},
{
"path": "sphinx_js/suffix_tree.py",
"chars": 6433,
"preview": "from collections.abc import Iterable, Sequence\nfrom typing import (\n Any,\n Generic,\n TypedDict,\n TypeVar,\n)\n"
},
{
"path": "sphinx_js/templates/attribute.rst",
"chars": 641,
"preview": "{% import 'common.rst' as common %}\n\n{% if is_type_alias -%}\n.. js:typealias:: {{ name }}{{ type_params }}\n{%- else -%}\n"
},
{
"path": "sphinx_js/templates/class.rst",
"chars": 1046,
"preview": "{% import 'common.rst' as common %}\n\n{% if is_interface -%}\n.. js:interface:: {{ name }}{{ type_params }}{{ params }}\n{%"
},
{
"path": "sphinx_js/templates/common.rst",
"chars": 737,
"preview": "{% macro deprecated(message) %}\n{% if message -%}\n.. note::\n\n Deprecated\n {%- if message is string -%}: {{ message }"
},
{
"path": "sphinx_js/templates/function.rst",
"chars": 514,
"preview": "{% import 'common.rst' as common %}\n\n.. js:function:: {{ name }}{{ '?' if is_optional else '' }}{{ type_params }}{{ para"
},
{
"path": "sphinx_js/typedoc.py",
"chars": 6863,
"preview": "\"\"\"Converter from TypeDoc output to IR format\"\"\"\n\nimport os\nimport pathlib\nimport re\nimport subprocess\nfrom collections."
},
{
"path": "tests/__init__.py",
"chars": 85,
"preview": "from pytest import register_assert_rewrite\n\nregister_assert_rewrite(\"tests.testing\")\n"
},
{
"path": "tests/conftest.py",
"chars": 1083,
"preview": "import os\nimport sys\nfrom pathlib import Path\n\nimport sphinx\n\nSPHINX_VERSION = tuple(int(x) for x in sphinx.__version__."
},
{
"path": "tests/roots/test-incremental_js/a.js",
"chars": 129,
"preview": "/**\n * Class doc.\n */\nclass ClassA {\n /**\n * Static.\n */\n static noUseOfThis() {}\n\n /**\n * Here.\n */\n method"
},
{
"path": "tests/roots/test-incremental_js/a.rst",
"chars": 44,
"preview": "module a\n========\n\n.. js:autoclass:: ClassA\n"
},
{
"path": "tests/roots/test-incremental_js/a_b.rst",
"chars": 105,
"preview": "modules a and b\n===============\n\n.. js:autofunction:: ClassA#methodA\n.. js:autofunction:: ClassB#methodB\n"
},
{
"path": "tests/roots/test-incremental_js/b.rst",
"chars": 44,
"preview": "module b\n========\n\n.. js:autoclass:: ClassB\n"
},
{
"path": "tests/roots/test-incremental_js/conf.py",
"chars": 431,
"preview": "extensions = [\"sphinx_js\"]\n\n# Minimal stuff needed for Sphinx to work:\nsource_suffix = \".rst\"\nmaster_doc = \"index\"\nautho"
},
{
"path": "tests/roots/test-incremental_js/index.rst",
"chars": 81,
"preview": "Table of Contents\n-----------------\n\n.. toctree::\n\n a\n b\n a_b\n unrelated\n"
},
{
"path": "tests/roots/test-incremental_js/inner/b.js",
"chars": 129,
"preview": "/**\n * Class doc.\n */\nclass ClassB {\n /**\n * Static.\n */\n static noUseOfThis() {}\n\n /**\n * Here.\n */\n method"
},
{
"path": "tests/roots/test-incremental_js/jsdoc.json",
"chars": 63,
"preview": "{\n \"source\": {\n \"includePattern\": \".+\\\\.js(doc|x)?$\"\n }\n}\n"
},
{
"path": "tests/roots/test-incremental_js/unrelated.rst",
"chars": 20,
"preview": "unrelated\n=========\n"
},
{
"path": "tests/roots/test-incremental_ts/a.rst",
"chars": 44,
"preview": "module a\n========\n\n.. js:autoclass:: ClassA\n"
},
{
"path": "tests/roots/test-incremental_ts/a.ts",
"chars": 82,
"preview": "export class ClassA {\n constructor() {}\n\n /**\n * Here.\n */\n methodA() {}\n}\n"
},
{
"path": "tests/roots/test-incremental_ts/a_b.rst",
"chars": 105,
"preview": "modules a and b\n===============\n\n.. js:autofunction:: ClassA#methodA\n.. js:autofunction:: ClassB#methodB\n"
},
{
"path": "tests/roots/test-incremental_ts/b.rst",
"chars": 44,
"preview": "module b\n========\n\n.. js:autoclass:: ClassB\n"
},
{
"path": "tests/roots/test-incremental_ts/conf.py",
"chars": 461,
"preview": "extensions = [\"sphinx_js\"]\n\n# Minimal stuff needed for Sphinx to work:\nsource_suffix = \".rst\"\nmaster_doc = \"index\"\nautho"
},
{
"path": "tests/roots/test-incremental_ts/index.rst",
"chars": 81,
"preview": "Table of Contents\n-----------------\n\n.. toctree::\n\n a\n b\n a_b\n unrelated\n"
},
{
"path": "tests/roots/test-incremental_ts/inner/b.ts",
"chars": 82,
"preview": "export class ClassB {\n constructor() {}\n\n /**\n * Here.\n */\n methodB() {}\n}\n"
},
{
"path": "tests/roots/test-incremental_ts/tsconfig.json",
"chars": 109,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es6\",\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\"\n }\n}\n"
},
{
"path": "tests/roots/test-incremental_ts/unrelated.rst",
"chars": 20,
"preview": "unrelated\n=========\n"
},
{
"path": "tests/sphinxJsConfig.ts",
"chars": 102,
"preview": "export const config = {\n shouldDestructureArg: (param) => param.name === \"destructureThisPlease\",\n};\n"
},
{
"path": "tests/test.ts",
"chars": 3523,
"preview": "import assert from \"node:assert\";\nimport { test, suite, before } from \"node:test\";\nimport { run } from \"../sphinx_js/js/"
},
{
"path": "tests/test_build_js/source/code.js",
"chars": 4404,
"preview": "/**\n * Return the ratio of the inline text length of the links in an element to\n * the inline text length of the entire "
},
{
"path": "tests/test_build_js/source/docs/autoattribute.rst",
"chars": 46,
"preview": ".. js:autoattribute:: ContainingClass#someVar\n"
},
{
"path": "tests/test_build_js/source/docs/autoattribute_deprecated.rst",
"chars": 96,
"preview": ".. js:autoattribute:: DeprecatedAttribute\n\n.. js:autoattribute:: DeprecatedExplanatoryAttribute\n"
},
{
"path": "tests/test_build_js/source/docs/autoattribute_example.rst",
"chars": 39,
"preview": ".. js:autoattribute:: ExampleAttribute\n"
},
{
"path": "tests/test_build_js/source/docs/autoattribute_see.rst",
"chars": 35,
"preview": ".. js:autoattribute:: SeeAttribute\n"
},
{
"path": "tests/test_build_js/source/docs/autoclass.rst",
"chars": 34,
"preview": ".. js:autoclass:: ContainingClass\n"
},
{
"path": "tests/test_build_js/source/docs/autoclass_alphabetical.rst",
"chars": 47,
"preview": ".. js:autoclass:: NonAlphabetical\n :members:\n"
},
{
"path": "tests/test_build_js/source/docs/autoclass_deprecated.rst",
"chars": 80,
"preview": ".. js:autoclass:: DeprecatedClass\n\n.. js:autoclass:: DeprecatedExplanatoryClass\n"
},
{
"path": "tests/test_build_js/source/docs/autoclass_example.rst",
"chars": 31,
"preview": ".. js:autoclass:: ExampleClass\n"
},
{
"path": "tests/test_build_js/source/docs/autoclass_exclude_members.rst",
"chars": 85,
"preview": ".. js:autoclass:: ClosedClass\n :members:\n :exclude-members: publical2, publical3\n"
},
{
"path": "tests/test_build_js/source/docs/autoclass_members.rst",
"chars": 47,
"preview": ".. js:autoclass:: ContainingClass\n :members:\n"
},
{
"path": "tests/test_build_js/source/docs/autoclass_members_list.rst",
"chars": 63,
"preview": ".. js:autoclass:: ClosedClass\n :members: publical3, publical\n"
},
{
"path": "tests/test_build_js/source/docs/autoclass_members_list_star.rst",
"chars": 66,
"preview": ".. js:autoclass:: ContainingClass\n :members: bar, *, someMethod\n"
},
{
"path": "tests/test_build_js/source/docs/autoclass_no_paramnames.rst",
"chars": 115,
"preview": "Make sure we don't have KeyErrors on naked, memberless objects labeled as classes:\n\n.. js:autoclass:: NoParamnames\n"
},
{
"path": "tests/test_build_js/source/docs/autoclass_private_members.rst",
"chars": 68,
"preview": ".. js:autoclass:: ContainingClass\n :members:\n :private-members:\n"
},
{
"path": "tests/test_build_js/source/docs/autoclass_see.rst",
"chars": 27,
"preview": ".. js:autoclass:: SeeClass\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_callback.rst",
"chars": 37,
"preview": ".. js:autofunction:: requestCallback\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_defaults_code.rst",
"chars": 46,
"preview": ".. js:autofunction:: defaultsDocumentedInCode\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_defaults_doclet.rst",
"chars": 48,
"preview": ".. js:autofunction:: defaultsDocumentedInDoclet\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_deprecated.rst",
"chars": 92,
"preview": ".. js:autofunction:: deprecatedFunction\n\n.. js:autofunction:: deprecatedExplanatoryFunction\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_destructured_params.rst",
"chars": 40,
"preview": ".. js:autofunction:: destructuredParams\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_example.rst",
"chars": 32,
"preview": ".. js:autofunction:: exampleTag\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_explicit.rst",
"chars": 123,
"preview": ".. js:autofunction:: linkDensity(snorko, borko[, forko])\n\n Things are ``neat``.\n\n Off the beat.\n\n * Sweet\n * Fle"
},
{
"path": "tests/test_build_js/source/docs/autofunction_long.rst",
"chars": 48,
"preview": ".. js:autofunction:: ContainingClass#someMethod\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_minimal.rst",
"chars": 33,
"preview": ".. js:autofunction:: linkDensity\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_see.rst",
"chars": 33,
"preview": ".. js:autofunction:: seeFunction\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_short.rst",
"chars": 64,
"preview": ".. js:autofunction:: ContainingClass#someMethod\n :short-name:\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_static.rst",
"chars": 44,
"preview": ".. js:autoclass:: SimpleClass\n :members:\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_typedef.rst",
"chars": 36,
"preview": ".. js:autofunction:: TypeDefinition\n"
},
{
"path": "tests/test_build_js/source/docs/autofunction_variadic.rst",
"chars": 39,
"preview": ".. js:autofunction:: variadicParameter\n"
},
{
"path": "tests/test_build_js/source/docs/avoid_shadowing.rst",
"chars": 38,
"preview": ".. js:autofunction:: more_code.shadow\n"
},
{
"path": "tests/test_build_js/source/docs/conf.py",
"chars": 192,
"preview": "extensions = [\"sphinx_js\"]\n\n# Minimal stuff needed for Sphinx to work:\nsource_suffix = \".rst\"\nmaster_doc = \"index\"\nautho"
},
{
"path": "tests/test_build_js/source/docs/getter_setter.rst",
"chars": 42,
"preview": ".. js:autoattribute:: ContainingClass#bar\n"
},
{
"path": "tests/test_build_js/source/docs/index.rst",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_build_js/source/docs/injection.rst",
"chars": 31,
"preview": ".. js:autofunction:: injection\n"
},
{
"path": "tests/test_build_js/source/docs/union.rst",
"chars": 27,
"preview": ".. js:autofunction:: union\n"
},
{
"path": "tests/test_build_js/source/docs/unwrapped.rst",
"chars": 38,
"preview": ".. js:autofunction:: longDescriptions\n"
},
{
"path": "tests/test_build_js/source/more_code.js",
"chars": 169,
"preview": "// This file is processed after code.js and so tends to shadow things.\n\n/** Another thing named shadow, to threaten to s"
},
{
"path": "tests/test_build_js/test_build_js.py",
"chars": 15662,
"preview": "from sphinx import __version__ as sphinx_version\n\nfrom tests.testing import SphinxBuildTestCase\n\nSPHINX_VERSION = tuple("
},
{
"path": "tests/test_build_ts/source/class.ts",
"chars": 2554,
"preview": "/**\n * A definition of a class\n */\nexport class ClassDefinition {\n field: string;\n\n /**\n * ClassDefinition construct"
},
{
"path": "tests/test_build_ts/source/docs/async_function.rst",
"chars": 35,
"preview": ".. js:autofunction:: asyncFunction\n"
},
{
"path": "tests/test_build_ts/source/docs/autoclass_class_with_interface_and_supers.rst",
"chars": 58,
"preview": ".. js:autoclass:: ClassWithSupersAndInterfacesAndAbstract\n"
},
{
"path": "tests/test_build_ts/source/docs/autoclass_constructorless.rst",
"chars": 39,
"preview": ".. js:autoclass:: ConstructorlessClass\n"
},
{
"path": "tests/test_build_ts/source/docs/autoclass_exported.rst",
"chars": 32,
"preview": ".. js:autoclass:: ExportedClass\n"
},
{
"path": "tests/test_build_ts/source/docs/autoclass_interface_optionals.rst",
"chars": 46,
"preview": ".. js:autoclass:: OptionalThings\n :members:\n"
},
{
"path": "tests/test_build_ts/source/docs/autoclass_star.rst",
"chars": 58,
"preview": ".. js:autoclass:: ClassDefinition\n :members: method1, *\n"
},
{
"path": "tests/test_build_ts/source/docs/automodule.rst",
"chars": 26,
"preview": ".. js:automodule:: module\n"
},
{
"path": "tests/test_build_ts/source/docs/autosummary.rst",
"chars": 27,
"preview": ".. js:autosummary:: module\n"
},
{
"path": "tests/test_build_ts/source/docs/conf.py",
"chars": 599,
"preview": "extensions = [\"sphinx_js\"]\n\n# Minimal stuff needed for Sphinx to work:\nsource_suffix = \".rst\"\nmaster_doc = \"index\"\nautho"
},
{
"path": "tests/test_build_ts/source/docs/deprecated.rst",
"chars": 83,
"preview": ".. js:autofunction:: deprecatedFunction1\n\n.. js:autofunction:: deprecatedFunction2\n"
},
{
"path": "tests/test_build_ts/source/docs/example.rst",
"chars": 37,
"preview": ".. js:autofunction:: exampleFunction\n"
},
{
"path": "tests/test_build_ts/source/docs/getset.rst",
"chars": 42,
"preview": ".. js:autoclass:: GetSetDocs\n :members:\n"
},
{
"path": "tests/test_build_ts/source/docs/index.rst",
"chars": 248,
"preview": ".. js:autoclass:: ClassDefinition\n :members:\n\n The link from the superclass in autoclass_class_with_interface_and_su"
},
{
"path": "tests/test_build_ts/source/docs/inherited_docs.rst",
"chars": 78,
"preview": ".. js:autoclass:: Base\n :members:\n\n.. js:autoclass:: Extension\n :members:\n"
},
{
"path": "tests/test_build_ts/source/docs/predicate.rst",
"chars": 31,
"preview": ".. js:autofunction:: predicate\n"
},
{
"path": "tests/test_build_ts/source/docs/sphinx_link_in_description.rst",
"chars": 44,
"preview": ".. js:autofunction:: spinxLinkInDescription\n"
},
{
"path": "tests/test_build_ts/source/docs/symbol.rst",
"chars": 40,
"preview": ".. js:autoclass:: Iterable\n :members:\n"
},
{
"path": "tests/test_build_ts/source/docs/xrefs.rst",
"chars": 110,
"preview": ".. js:autofunction:: blah\n\n blah\n\n.. js:autofunction:: thunk\n\n Xrefs in the function type of the argument\n"
},
{
"path": "tests/test_build_ts/source/empty.ts",
"chars": 54,
"preview": "// Test that we don't crash on a file with no exports\n"
},
{
"path": "tests/test_build_ts/source/module.ts",
"chars": 1515,
"preview": "/**\n * The thing.\n */\nexport const a = 7;\n\n/**\n * Clutches the bundle\n */\nexport async function f() {}\n\nexport function "
},
{
"path": "tests/test_build_ts/source/tsconfig.json",
"chars": 109,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es6\",\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\"\n }\n}\n"
},
{
"path": "tests/test_build_ts/source/typedoc.json",
"chars": 3,
"preview": "{}\n"
},
{
"path": "tests/test_build_ts/test_build_ts.py",
"chars": 13743,
"preview": "from textwrap import dedent\n\nfrom bs4 import BeautifulSoup, Tag\n\nfrom tests.testing import SphinxBuildTestCase\n\n\nclass T"
},
{
"path": "tests/test_build_xref_none/source/docs/conf.py",
"chars": 318,
"preview": "extensions = [\"sphinx_js\"]\n\njs_language = \"typescript\"\njs_source_path = [\"../main.ts\"]\nroot_for_relative_js_paths = \"../"
},
{
"path": "tests/test_build_xref_none/source/docs/index.rst",
"chars": 122,
"preview": "An extra line so we can test whether the error points to the right line.\nAnother extra line.\n\n.. js:autoattribute:: thin"
},
{
"path": "tests/test_build_xref_none/source/main.ts",
"chars": 64,
"preview": "/** A simple variable to document. */\nexport let thing: number;\n"
},
{
"path": "tests/test_build_xref_none/test_build_xref_none.py",
"chars": 944,
"preview": "\"\"\"\nRST errors from ts_type_xref_formatter should cause build failures.\n\nThe test conf.py uses a formatter that always r"
},
{
"path": "tests/test_common_mark/source/code.js",
"chars": 34,
"preview": "/**\n * Foo.\n */\nfunction foo() {}\n"
},
{
"path": "tests/test_common_mark/source/docs/conf.py",
"chars": 640,
"preview": "from recommonmark.transform import AutoStructify\n\nextensions = [\"recommonmark\", \"sphinx.ext.mathjax\", \"sphinx_js\"]\nsourc"
},
{
"path": "tests/test_common_mark/source/docs/index.md",
"chars": 41,
"preview": "```eval_rst\n.. js:autofunction:: foo\n```\n"
},
{
"path": "tests/test_common_mark/test_common_mark.py",
"chars": 328,
"preview": "from tests.testing import SphinxBuildTestCase\n\n\nclass Tests(SphinxBuildTestCase):\n def test_build_success(self):\n "
},
{
"path": "tests/test_dot_dot_paths/source/code.js",
"chars": 46,
"preview": "/**\n * Bar function\n */\nfunction bar(node) {}\n"
},
{
"path": "tests/test_dot_dot_paths/source/docs/conf.py",
"chars": 226,
"preview": "extensions = [\"sphinx_js\"]\n\n# Minimal stuff needed for Sphinx to work:\nsource_suffix = \".rst\"\nmaster_doc = \"index\"\nautho"
},
{
"path": "tests/test_dot_dot_paths/source/docs/index.rst",
"chars": 25,
"preview": ".. js:autofunction:: bar\n"
},
{
"path": "tests/test_dot_dot_paths/test_dot_dot_paths.py",
"chars": 416,
"preview": "from tests.testing import SphinxBuildTestCase\n\n\nclass Tests(SphinxBuildTestCase):\n def test_dot_dot(self):\n \"\""
},
{
"path": "tests/test_incremental.py",
"chars": 3023,
"preview": "\"\"\"Test incremental builds.\"\"\"\n\nimport warnings\nfrom pathlib import Path\n\nimport pytest\nfrom sphinx.environment import C"
},
{
"path": "tests/test_init.py",
"chars": 464,
"preview": "import pytest\nfrom sphinx.errors import SphinxError\n\nfrom sphinx_js import root_or_fallback\n\n\ndef test_relative_path_roo"
},
{
"path": "tests/test_ir.py",
"chars": 3050,
"preview": "from inspect import getmembers\nfrom json import dumps, loads\n\nimport pytest\n\nfrom sphinx_js.ir import (\n Attribute,\n "
},
{
"path": "tests/test_jsdoc_analysis/source/class.js",
"chars": 584,
"preview": "/**\n * This is a long description that should not be unwrapped. Once day, I was\n * walking down the street, and a large,"
},
{
"path": "tests/test_jsdoc_analysis/source/function.js",
"chars": 465,
"preview": "/**\n * Determine any of type, note, score, and element using a callback. This\n * overrides any previous call.\n *\n * The "
},
{
"path": "tests/test_jsdoc_analysis/test_jsdoc.py",
"chars": 5289,
"preview": "from sphinx_js.ir import (\n Attribute,\n DescriptionCode,\n Exc,\n Function,\n Param,\n Pathname,\n Retur"
},
{
"path": "tests/test_parsers.py",
"chars": 684,
"preview": "from sphinx_js.parsers import PathVisitor, path_and_formal_params\n\n\ndef test_escapes():\n r\"\"\"Make sure escapes work r"
},
{
"path": "tests/test_paths.py",
"chars": 3673,
"preview": "import subprocess\nfrom pathlib import Path\n\nimport pytest\nfrom conftest import TYPEDOC_VERSION\nfrom sphinx.errors import"
},
{
"path": "tests/test_renderers.py",
"chars": 15912,
"preview": "from textwrap import dedent, indent\nfrom typing import Any\n\nimport pytest\nfrom sphinx.util import rst\n\nfrom sphinx_js.ir"
},
{
"path": "tests/test_suffix_tree.py",
"chars": 2132,
"preview": "from pytest import raises\n\nfrom sphinx_js.suffix_tree import SuffixAmbiguous, SuffixNotFound, SuffixTree\n\n\ndef test_thin"
},
{
"path": "tests/test_testing.py",
"chars": 354,
"preview": "from tests.testing import NO_MATCH, dict_where\n\n\ndef test_dict_where():\n json = {\"hi\": \"there\", \"more\": {\"mister\": \"z"
},
{
"path": "tests/test_typedoc_analysis/source/exports.ts",
"chars": 52,
"preview": "export interface Blah {\n a: number;\n b: string;\n}\n"
},
{
"path": "tests/test_typedoc_analysis/source/nodes.ts",
"chars": 1138,
"preview": "export class Superclass {\n method() {}\n}\n\nexport interface SuperInterface {}\n\nexport interface Interface extends SuperI"
},
{
"path": "tests/test_typedoc_analysis/source/subdir/pathSegments.ts",
"chars": 866,
"preview": "/**\n * Function\n */\nexport function foo(): void {\n /**\n * An inner function\n */\n function inner(): void {}\n}\nfoo.a"
},
{
"path": "tests/test_typedoc_analysis/source/tsconfig.json",
"chars": 109,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es6\",\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\"\n }\n}\n"
},
{
"path": "tests/test_typedoc_analysis/source/types.ts",
"chars": 6013,
"preview": "// Basic types: https://www.typescriptlang.org/docs/handbook/basic-types.html\n\nimport { Blah } from \"./exports\";\n\nexport"
},
{
"path": "tests/test_typedoc_analysis/test_typedoc_analysis.py",
"chars": 27574,
"preview": "from copy import copy, deepcopy\n\nimport pytest\n\nfrom sphinx_js.ir import (\n Attribute,\n Class,\n Description,\n "
},
{
"path": "tests/testing.py",
"chars": 5505,
"preview": "import sys\nfrom inspect import getmembers\nfrom os.path import dirname, join\nfrom shutil import rmtree\n\nfrom sphinx.cmd.b"
}
]
About this extraction
This page contains the full source code of the mozilla/sphinx-js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 159 files (345.3 KB), approximately 89.2k tokens, and a symbol index with 709 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.