[
  {
    "path": ".editorconfig",
    "content": "# http://editorconfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\ninsert_final_newline = true\ncharset = utf-8\nend_of_line = lf\n\n[*.py]\nindent_size = 4\n\n[*.rst]\nindent_size = 4\n"
  },
  {
    "path": ".github/codecov.yml",
    "content": "comment: false\ncodecov:\n  branch: main\n  require_ci_to_pass: false\n  notify:\n    wait_for_ci: false\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# Keep GitHub Actions up to date with GitHub's Dependabot...\n# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot\n# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem\nversion: 2\nupdates:\n  - package-ecosystem: github-actions\n    directory: /\n    groups:\n      github-actions:\n        patterns:\n          - \"*\" # Group all Actions updates into a single larger pull request\n    schedule:\n      interval: weekly\n"
  },
  {
    "path": ".github/workflows/check_ts.yml",
    "content": "name: check-ts\non:\n  pull_request:\npermissions: write-all\n\njobs:\n  ts:\n    if: false\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - name: Get diff lines\n        id: diff\n        uses: Equip-Collaboration/diff-line-numbers@v1.1.0\n        with:\n          include: '[\"\\\\.ts$\"]'\n      - name: Detecting files changed\n        id: files\n        uses: umani/changed-files@v4.2.0\n        with:\n          repo-token: ${{ github.token }}\n          pattern: '^.*\\.ts$'\n      - name: List files changed (you can remove this step, for monitoring only)\n        run: |\n          echo 'Files modified: ${{steps.files.outputs.files_updated}}'\n          echo 'Files added: ${{steps.files.outputs.files_created}}'\n          echo 'Files removed: ${{steps.files.outputs.files_deleted}}'\n      - uses: Arhia/action-check-typescript@v1.1.0\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          use-check: true\n          check-fail-mode: added\n          files-changed: ${{steps.files.outputs.files_updated}}\n          files-added: ${{steps.files.outputs.files_created}}\n          files-deleted: ${{steps.files.outputs.files_deleted}}\n          line-numbers: ${{steps.diff.outputs.lineNumbers}}\n          output-behaviour: both\n          comment-behaviour: new\n          ts-config-path: \"./sphinx_js/js/tsconfig.json\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "---\nname: CI\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    continue-on-error: true\n\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    name: Python ${{ matrix.python-version}}\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Python\n        uses: actions/setup-python@v6.0.0\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Update pip and install dev requirements\n        run: |\n          python -m pip install --upgrade pip\n          pip install nox\n\n      - name: Test\n        run: nox -s tests-${{ matrix.python-version }}\n\n      - name: Upload coverage reports to Codecov\n        uses: codecov/codecov-action@v5\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n\n      - uses: actions/upload-artifact@v5\n        if: success() || failure()\n        with:\n          name: test-results-${{ matrix.python-version }}\n          path: test-results.xml\n\n  test-typedoc-versions:\n    runs-on: ubuntu-latest\n\n    continue-on-error: true\n\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.12\"]\n        typedoc-version: [\"0.25\", \"0.26\", \"0.27\", \"0.28\"]\n\n    name: Python ${{ matrix.python-version}} + typedoc ${{ matrix.typedoc-version }}\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: 22\n\n      - name: Set up Python\n        uses: actions/setup-python@v6.0.0\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Update pip and install dev requirements\n        run: |\n          python -m pip install --upgrade pip\n          pip install nox\n      - name: Test\n        run: nox -s \"test_typedoc-${{ matrix.python-version }}(typedoc='${{ matrix.typedoc-version }}')\"\n\n      - uses: actions/upload-artifact@v5\n        if: success() || failure()\n        with:\n          name: test_typedoc-results-${{ matrix.python-version }}-${{ matrix.typedoc-version }}\n          path: test-results.xml\n\n  test-sphinx-versions:\n    runs-on: ubuntu-latest\n\n    continue-on-error: true\n\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.12\"]\n        sphinx-version: [\"6\"]\n\n    name: Test sphinx 6\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Python\n        uses: actions/setup-python@v6.0.0\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Update pip and install dev requirements\n        run: |\n          python -m pip install --upgrade pip\n          pip install nox\n      - name: Test\n        run: nox -s \"test_sphinx_6-${{ matrix.python-version }}\"\n\n      - uses: actions/upload-artifact@v5\n        if: success() || failure()\n        with:\n          name: test_sphinx_6-${{ matrix.python-version }}\n          path: test-results.xml\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: CD\n\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n  schedule:\n    - cron: \"0 3 * * 1\"\n\nenv:\n  FORCE_COLOR: 3\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v4.2.2\n        with:\n          # include tags so that hatch-vcs can infer the version\n          fetch-depth: 0\n          # switch to fetch-tags: true when the following is fixed\n          # see https://github.com/actions/checkout/issues/2041\n          # fetch-tags: true\n\n      - name: Setup Python\n        uses: actions/setup-python@bba65e51ff35d50c6dbaaacd8a4681db13aa7cb4 # v5.6.0\n        with:\n          python-version: \"3.12\"\n\n      - name: Build\n        run: |\n          python -m pip install build\n          python -m build .\n\n      - name: Store the distribution packages\n        uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0\n        with:\n          name: python-package-distributions\n          path: dist/\n          if-no-files-found: error\n\n  publish:\n    name: Publish to PyPI\n    needs: [build]\n    runs-on: ubuntu-latest\n    if: github.event_name == 'release' && github.event.action == 'published'\n    environment:\n      name: pypi\n      url: https://pypi.org/p/sphinx-js\n    permissions:\n      id-token: write # IMPORTANT: mandatory for trusted publishing\n      attestations: write\n      contents: read\n    steps:\n      - name: Download all the dists\n        uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0\n        with:\n          path: dist/\n          merge-multiple: true\n\n      - name: Generate artifact attestations\n        uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0\n        with:\n          subject-path: \"dist/*\"\n\n      - name: Publish distribution 📦 to PyPI\n        uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0\n"
  },
  {
    "path": ".github/workflows/test_report.yml",
    "content": "name: \"Test Report\"\non:\n  workflow_run:\n    workflows: [\"CI\"]\n    types:\n      - completed\njobs:\n  report:\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\"]\n\n    runs-on: ubuntu-latest\n    steps:\n      - uses: dorny/test-reporter@v2.2.0\n        with:\n          artifact: test-results-${{ matrix.python-version }}\n          name: Test report - ${{ matrix.python-version }}\n          path: test-results.xml\n          reporter: java-junit\n  report-typedoc:\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.12\"]\n        typedoc-version: [\"0.25\"]\n\n    runs-on: ubuntu-latest\n    steps:\n      - uses: dorny/test-reporter@v2.2.0\n        with:\n          artifact: test_typedoc-results-${{ matrix.python-version }}-${{ matrix.typedoc-version }}\n          name: Test report - Python ${{ matrix.python-version}} + typedoc ${{ matrix.typedoc-version }}\n          path: test-results.xml\n          reporter: java-junit\n  report-sphinx:\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.12\"]\n        sphinx-version: [\"6\"]\n\n    runs-on: ubuntu-latest\n    steps:\n      - uses: dorny/test-reporter@v2.2.0\n        with:\n          artifact: test_sphinx_6-${{ matrix.python-version }}\n          name: Test report - Sphinx 6\n          path: test-results.xml\n          reporter: java-junit\n"
  },
  {
    "path": ".gitignore",
    "content": "/.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.7\n*.pyc\n# Pycharm config\n.idea\n# VsCode config\n.vscode\n.DS_Store\nvenv\n.venv\ncoverage.xml\ntest-results.xml\nnode_modules\ntsconfig.tsbuildinfo\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "default_language_version:\n  python: \"3.10\"\nrepos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: \"v4.4.0\"\n    hooks:\n      - id: check-added-large-files\n      - id: check-case-conflict\n      - id: check-merge-conflict\n        exclude: README.MD\n      - id: check-symlinks\n      - id: check-yaml\n      - id: debug-statements\n      - id: end-of-file-fixer\n      - id: mixed-line-ending\n      - id: trailing-whitespace\n\n  - repo: https://github.com/astral-sh/ruff-pre-commit\n    rev: \"v0.9.1\"\n    hooks:\n      - id: ruff\n        args: [--fix]\n      - id: ruff-format\n\n  - repo: https://github.com/biomejs/pre-commit\n    rev: \"v0.6.1\"\n    hooks:\n      - id: biome-format\n        additional_dependencies: [\"@biomejs/biome@2.1.1\"]\n        types_or: [javascript, ts]\n\n  - repo: https://github.com/rstcheck/rstcheck\n    rev: \"v6.2.5\"\n    hooks:\n      - id: rstcheck\n        exclude: (tests/|sphinx_js/templates)\n        additional_dependencies: [\"rstcheck[sphinx,toml]\"]\n\n  - repo: https://github.com/codespell-project/codespell\n    rev: \"v2.2.5\"\n    hooks:\n      - id: codespell\n\n  - repo: https://github.com/pre-commit/mirrors-mypy\n    rev: \"v1.5.1\"\n    hooks:\n      - id: mypy\n        exclude: (tests/)\n        args: []\n        additional_dependencies:\n          - attrs\n          - cattrs\n          - jinja2\n          - nox\n          - pydantic\n          - pytest\n          - sphinx\n          - types-docutils\n          - types-parsimonious\n          - types-setuptools\n\nci:\n  autoupdate_schedule: \"quarterly\"\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## 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- Fix: Errors generated from invalid xrefs should fail build (#300)\n\n### 5.0.2: (October 17th, 2025)\n\n- Unpin markupsafe by @fmhoeger (#287)\n\n### 5.0.1: (September 17th, 2025)\n- Fixed a bug that comment of the arrow function in the interface is not rendered correctly.\n  (pyodide/sphinx-js#284)\n\n### 5.0.0: (July 2nd, 2025)\n\n- Dropped support for Python 3.9 (pyodide/sphinx-js-fork#7)\n- Dropped support for typedoc 0.15, added support for typedoc 0.25--0.28 (\n  pyodide/sphinx-js-fork#11, pyodide/sphinx-js-fork#22,\n  pyodide/sphinx-js-fork#31, pyodide/sphinx-js-fork#39,\n  pyodide/sphinx-js-fork#41, pyodide/sphinx-js-fork#43\n  pyodide/sphinx-js-fork#52, pyodide/sphinx-js-fork#53,\n  pyodide/sphinx-js-fork#54, pyodide/sphinx-js-fork#174,\n  #266)\n- Added handling for TypeScript type parameters and type bounds.\n  (pyodide/sphinx-js-fork#25)\n- Only monkeypatch Sphinx classes when sphinx_js extension is used\n  (pyodide/sphinx-js-fork#27)\n- Allow using installation of `typedoc` or `jsdoc` from `node_modules`\n  instead of requiring global install. (pyodide/sphinx-js-fork#33)\n- Handle markdown style codepens correctly in typedoc comments.\n  (pyodide/sphinx-js-fork#47)\n- Added support for destructuring the documentation of keyword arguments in\n  TypeScript using the `@destructure` tag or the\n  `shouldDestructureArg` hook. (\n  pyodide/sphinx-js-fork#48, pyodide/sphinx-js-fork#74,\n  pyodide/sphinx-js-fork#75, pyodide/sphinx-js-fork#101,\n  pyodide/sphinx-js-fork#128)\n- Added rendering for cross references in TypeScript types. (\n  pyodide/sphinx-js-fork#51, pyodide/sphinx-js-fork#56,\n  pyodide/sphinx-js-fork#67, pyodide/sphinx-js-fork#81,\n  pyodide/sphinx-js-fork#82, pyodide/sphinx-js-fork#83,\n  pyodide/sphinx-js-fork#153, pyodide/sphinx-js-fork#160)\n- Added rendering for function types in TypeScript documentation. (\n  pyodide/sphinx-js-fork#55, pyodide/sphinx-js-fork#58,\n  pyodide/sphinx-js-fork#59)\n- Add async prefix to async functions (pyodide/sphinx-js-fork#65).\n- Added the `sphinx-js_type` css class around all types in documentation. This\n  allows applying custom css just to types (pyodide/sphinx-js-fork#85)\n- Added `ts_type_bold` config option that applies css to `.sphinx-js_type`\n  to render all types as bold.\n- Added `js:automodule` directive (pyodide/sphinx-js-fork#108)\n- Added `js:autosummary` directive (pyodide/sphinx-js-fork#109)\n- Added rendering for `queryType` (e.g., `let y: typeof x;`)\n  (pyodide/sphinx-js-fork#124)\n- Added rendering for `typeOperator` (e.g., `let y: keyof x`)\n  (pyodide/sphinx-js-fork#125)\n- Fixed crash when objects are reexported. (pyodide/sphinx-js-fork#126)\n- Added `jsdoc_tsconfig_path` which can specify the path to the\n  `tsconfig.json` file that should be used. (pyodide/sphinx-js-fork#116)\n- Added a `js:interface` directive (pyodide/sphinx-js-fork#138).\n- Removed parentheses from xrefs to classes (pyodide/sphinx-js-fork#155).\n- Added a `:js:typealias:` directive (pyodide/sphinx-js-fork#156).\n- Added rendering for conditional, indexed access, inferred, mapped, optional,\n  rest, and template litreal types (pyodide/sphinx-js-fork#157).\n- Added readonly prefix to readonly properties (pyodide/sphinx-js-fork#158).\n\n### 4.0.0: (December 23rd, 2024)\n\n- Drop support for Python 3.8.\n- Add support for Python 3.12 and 3.13.\n- Add support for Sphinx 8.x.x.\n- Get CI working again.\n- Drop pin for MarkupSafe. (#244)\n- Add dependabot checking for GitHub actions. (Christian Clauss)\n- Fix wheel contents to not include tests. (#241)\n\nThank you to Will Kahn-Greene and Christian Clauss!\n\n### 3.2.2: (September 20th, 2023)\n\n- Remove Sphinx upper-bound requirement. (#227)\n- Drop support for Python 3.7. (#228)\n\nThank you to Will Kahn-Greene!\n\n### 3.2.1: (December 16th, 2022)\n\n- Fix xrefs to static functions. (#178)\n- Add support for jsdoc 4.0.0. (#215)\n\nThank you to xsjad0 and Will Kahn-Greene!\n\n### 3.2.0: (December 13th, 2022)\n\n- Add \"static\" in front of static methods.\n- Pin Jinja2 and markupsafe versions. (#190)\n- Track dependencies; do not read all documents. This improves speed of\n  incremental updates. (#194)\n- Support Python 3.10 and 3.11. (#186)\n- Support Sphinx >= 4.1.0. (#209)\n- Fix types warning for `js_source_path` configuration item. (#182)\n\nThank you Stefan 'hr' Berder, David Huggins-Daines, Nick Alexander,\nmariusschenzle, Erik Rose, lonnen, and Will Kahn-Greene!\n\n### 3.1.2: (April 15th, 2021)\n\n- Remove our declared dependency on `docutils` to work around the way pip's\n  greedy dependency resolver reacts to the latest version of Sphinx. pip\n  fails when pip-installing sphinx-js because pip sees our \"any version of\n  docutils\" declaration first (which resolves greedily to the latest version,\n  0.17) but later encounters Sphinx's apparently new `<0.17` constraint and\n  gives up. We can revert this when pip's `--use-feature=2020-resolver`\n  becomes the default.\n\n### 3.1.1: (March 23rd, 2021)\n\n- Rewrite large parts of the suffix tree that powers path lookup. This fixes\n  several crashes.\n\n### 3.1: (September 10th, 2020)\n\n- Re-architect language analysis. There is now a well-documented intermediate\n  representation between JSDoc- and TypeDoc-emitted JSON and the renderers.\n  This should make it much faster to merge PRs.\n- Rewrite much of the TypeScript analysis engine so it feeds into the new IR.\n\n  - TypeScript analysis used to crash if your codebase contained any\n    overloaded functions. This no longer happens; we now arbitrarily use only\n    the first function signature of each overloaded function.\n  - Add support for static properties on TS classes.\n  - Support variadic args in TS.\n  - Support intersection types (`foo & bar`) in TS.\n  - Remove the \"exported from\" module links from classes and interfaces.\n    Functions never had them. Let's see if we miss them.\n  - Pathnames for TypeScript objects no longer spuriously use `~` after the\n    filename path segment; now they use `.` as in JS.\n  - More generally, TS pathnames are now just like JS ones. There is no more\n    `external:` prefix in front of filenames or `module:` in front of\n    namespace names.\n  - TS analyzer no longer cares with the current working directory is.\n  - Tests now assert only what they care about rather than being brittle to\n    the point of prohibiting any change.\n\n- No longer show args in the arg list that are utterly uninformative, lacking\n  both description and type info.\n- Class attributes are now listed before methods unless manally ordered with\n  `:members:`.\n\n### 3.0.1: (August 10th, 2020)\n\n- Don't crash when encountering a `../` prefix on an object path. This can\n  happen behind the scenes when `root_for_relative_js_paths` is set inward\n  of the JS code.\n\n### 3.0: (July 14th, 2020)\n\n- Make compatible with Sphinx 3, which requires Python 3.\n- Drop support for Python 2.\n- Make sphinx-js not care what the current working directory is, except for\n  the TypeScript analyzer, which needs further work.\n- Properly RST-escape return types.\n\n### 2.8: (September 16th, 2019)\n\n- Display generic TypeScript types properly. Make fields come before methods.\n  (Paul Grau)\n- Combine constructor and class documentation at the top TypeScript classes.\n  (Sebastian Weigand)\n- Switch to pytest as the testrunner. (Sebastian Weigand)\n- Add optional caching of JSDoc output, for large codebases. (Patrick Browne)\n- Fix the display of union types in TypeScript. (Sebastian Weigand)\n- Fix parsing breakage that began in typedoc 0.14.0. (Paul Grau)\n- Fix a data-intake crash with TypeScript. (Cristiano Santos)\n\n### 2.7.1: (November 16th, 2018)\n\n- Fix a crash that would happen sometimes with UTF-8 on Windows. #67.\n- Always use conf.py's dir for JSDoc's working dir. #78. (Thomas Khyn)\n\n### 2.7: (August 2nd, 2018))\n\n- Add experimental TypeScript support. (Wim Yedema)\n\n### 2.6: (July 26th, 2018)\n\n- Add support for `@deprecated` and `@see`. (David Li)\n- Notice and document JS variadic params nicely. (David Li)\n- Add linter to codebase.\n\n### 2.5: (April 20th, 2018)\n\n- Use documented `@params` to help fill out the formal param list for a\n  function. This keeps us from missing params that use destructuring. (flozz)\n- Improve error reporting when JSDoc is missing.\n- Add extracted default values to generated formal param lists. (flozz and\n  erikrose)\n\n### 2.4: (March 21, 2018)\n\n- Support the `@example` tag. (lidavidm)\n- Work under Windows. Before, we could hardly find any documentation. (flozz)\n- Properly unwrap multiple-line JSDoc tags, even if they have Windows line\n  endings. (Wim Yedema)\n- Drop support for Python 3.3, since Sphinx has also done so.\n- Fix build-time crash when using recommonmark (for Markdown support) under\n  Sphinx >=1.7.1. (jamrizzi)\n\n### 2.3.1: (January 11th, 2018)\n\n- Find the `jsdoc` command on Windows, where it has a different name. Then\n  patch up process communication so it doesn't hang.\n\n### 2.3: (November 1st, 2017)\n\n- Add the ability to say \"\\*\" within the `autoclass :members:` option,\n  meaning \"and all the members that I didn't explicitly list\".\n\n### 2.2: (October 10th, 2017)\n\n- Add `autofunction` support for `@callback` tags. (krassowski)\n- Add experimental `autofunction` support for `@typedef` tags. (krassowski)\n- Add a nice error message for when JSDoc can't find any JS files.\n- Pin six more tightly so `python_2_unicode_compatible` is sure to be around.\n\n### 2.1: (August 30th, 2017)\n\n- Allow multiple folders in `js_source_path`. This is useful for gradually\n  migrating large projects, one folder at a time, to JSDoc. Introduce\n  `root_for_relative_js_paths` to keep relative paths unambiguous in the\n  face of multiple source paths.\n- Aggregate PathTaken errors, and report them all at once. This means you\n  don't have to run JSDoc repeatedly while cleaning up large projects.\n- Fix a bytes-vs-strings issue that crashed on versions of Python 3 before\n  3.6. (jhkennedy)\n- Tolerate JS files that have filename extensions other than \".js\". Before,\n  when combined with custom JSDoc configuration that ingested such files,\n  incorrect object pathnames were generated, which led to spurious \"No JSDoc\n  documentation was found for object ...\" errors.\n\n### 2.0.1: (July 13th, 2017)\n\n- Fix spurious syntax errors while loading large JSDoc output by writing it\n  to a temp file first. (jhkennedy)\n\n### 2.0: (May 4th, 2017)\n\n- Deal with ambiguous object paths. Symbols with identical JSDoc longnames\n  (such as two top-level things called \"foo\" in different files) will no\n  longer have one shadow the other. Introduce an unambiguous path convention\n  for referring to objects. Add a real parser to parse them rather than the\n  dirty tricks we were using before. Backward compatibility breaks a little,\n  because ambiguous references are now a fatal error, rather than quietly\n  referring to the last definition JSDoc happened to encounter.\n- Index everything into a suffix tree so you can use any unique path suffix\n  to refer to an object.\n- Other fallout of having a real parser:\n\n  - Stop supporting \"-\" as a namepath separator.\n  - No longer spuriously translate escaped separators in namepaths into dots.\n  - Otherwise treat paths and escapes properly. For example, we can now\n    handle symbols that contain \"(\".\n\n- Fix KeyError when trying to gather the constructor params of a plain old\n  object labeled as a `@class`.\n\n### 1.5.2: (March 22th, 2017)\n\n- Fix crash while warning that a specified longname isn't found.\n\n### 1.5.1: (March 20th, 2017)\n\n- Sort `:members:` alphabetically when an order is not explicitly specified.\n\n### 1.5: (March 17th, 2017)\n\n- Add `:members:` option to `autoclass`.\n- Add `:private-members:` and `:exclude-members:` options to go with it.\n- Significantly refactor to allow directive classes to talk to each other.\n\n### 1.4: (March 10th, 2017)\n\n- Add `jsdoc_config_path` option.\n\n### 1.3.1: (March 6th, 2017)\n\n- Tolerate @args and other info field lines that are wrapped in the source\n  code.\n- Cite the file and line of the source comment in Sphinx-emitted warnings and\n  errors.\n\n### 1.3: (February 21st, 2017)\n\n- Add `autoattribute` directive.\n\n### 1.2: (February 14th, 2017)\n\n- Always do full rebuilds; don't leave pages stale when JS code has changed\n  but the RSTs have not.\n- Make Python-3-compatible.\n- Add basic `autoclass` directive.\n\n### 1.1: (February 13th, 2017)\n\n- Add `:short-name:` option.\n\n### 1.0: (February 7th, 2017)\n\n- Initial release, with just `js:autofunction`\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\n## Conduct\n\nWe 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.\n\n- Please be kind and courteous. There’s no need to be mean or rude.\n- Please avoid using usernames that are overtly sexual or otherwise might detract from a friendly, safe, and welcoming environment for all.\n- Respect that people have differences of opinion and that every design or implementation choice carries trade-offs. There is seldom a single right answer.\n- 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.\n- 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)\n- 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.\n- 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.\n- Likewise spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.\n\n## Moderation\n\nThese 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.\n\n1. 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.)\n2. Remarks that moderators find inappropriate are not allowed, even if they do not break a rule explicitly listed in the code of conduct.\n3. Moderators will first respond to such remarks with a warning.\n4. If the warning is unheeded, the offending community member will be temporarily banned.\n5. If the community member comes back and continues to make trouble, they will be permanently banned.\n6. Moderators may choose at their discretion to un-ban the community member if they offer the offended party a genuine apology.\n7. 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.\n8. 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.\n9. 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.\n10. 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.\n11. 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.\n\nAdapted 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).\n"
  },
  {
    "path": "CONTRIBUTORS",
    "content": "sphinx-js was originally written and maintained by Erik Rose and various\ncontributors within and without the Mozilla Corporation and Foundation.\nIt is now part of the Pyodide organization.\n\nIt is currently maintained by Hood Chatham.\n\nMaintainer emeritus:\n\n* Will Kahn-Greene\n* Erik Rose\n* Lonnen\n\nContributors:\n\n* Cristiano Santos\n* David Huggins-Daines\n* David Li\n* Fabien LOISON\n* Igor Loskutov\n* Jam Risser\n* Jared Dillard\n* Joseph H Kennedy\n* krassowski\n* mariusschenzle\n* Mike Cooper\n* Nicholas Bollweg\n* Nick Alexander\n* Patrick Browne\n* Paul Grau\n* Robert Helmer\n* Sebastian Weigand\n* Staś Małolepszy\n* Stefan 'hr' Berder\n* s-weigand\n* Tavian Barnes\n* Thomas Khyn\n* Wim Yedema\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Mozilla Foundation\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# 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 domain your users are familiar with, JSDoc's alphabetical\nlist of routines might suffice. But in a larger project, it is useful to\nintersperse prose with your API docs without having to copy and paste things.\n\nsphinx-js lets you use the industry-leading [Sphinx](https://sphinx-doc.org/)\ndocumentation tool with JS projects. It provides a handful of directives,\npatterned after the Python-centric [autodoc](https://www.sphinx-doc.org/en/latest/ext/autodoc.html) ones, for pulling\nJSDoc-formatted documentation into reStructuredText pages. And, because you can\nkeep using JSDoc in your code, you remain compatible with the rest of your JS\ntooling, like Google's Closure Compiler.\n\nsphinx-js also works with TypeScript, using the TypeDoc tool in place of JSDoc\nand emitting all the type information you would expect.\n\n## Setup\n\n1. Install JSDoc (or TypeDoc if you're writing TypeScript):\n\n   ```bash\n   npm install jsdoc\n   ```\n\n   or:\n\n   ```bash\n   npm install typedoc@0.28\n   ```\n\n   JSDoc 3.6.3 and 4.0.0 and TypeDoc 0.25--0.28 are known to work.\n\n2. Install sphinx-js, which will pull in Sphinx itself as a dependency:\n\n   ```bash\n   pip install sphinx-js\n   ```\n\n3. Make a documentation folder in your project by running `sphinx-quickstart`\n   and answering its questions:\n\n   ```bash\n   cd my-project\n   sphinx-quickstart\n   ```\n\n   ```\n   Please enter values for the following settings (just press Enter to\n   accept a default value, if one is given in brackets).\n\n   Selected root path: .\n\n   You have two options for placing the build directory for Sphinx output.\n   Either, you use a directory \"_build\" within the root path, or you separate\n   \"source\" and \"build\" directories within the root path.\n   > Separate source and build directories (y/n) [n]:\n\n   The project name will occur in several places in the built documentation.\n   > Project name: My Project\n   > Author name(s): Fred Fredson\n   > Project release []: 1.0\n\n   If the documents are to be written in a language other than English,\n   you can select a language here by its language code. Sphinx will then\n   translate text that it generates into that language.\n\n   For a list of supported codes, see\n   https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-language.\n   > Project language [en]:\n\n   Selected root path: .\n\n   You have two options for placing the build directory for Sphinx output.\n   Either, you use a directory \"_build\" within the root path, or you separate\n   \"source\" and \"build\" directories within the root path.\n   > Separate source and build directories (y/n) [n]:\n\n   The project name will occur in several places in the built documentation.\n   > Project name: My Project\n   > Author name(s): Fred Fredson\n   > Project release []: 1.0\n\n   If the documents are to be written in a language other than English,\n   you can select a language here by its language code. Sphinx will then\n   translate text that it generates into that language.\n\n   For a list of supported codes, see\n   https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-language.\n   > Project language [en]:\n   ```\n\n4. In the generated Sphinx `conf.py` file, turn on `sphinx_js` by adding it\n   to `extensions`:\n\n   ```python\n   extensions = ['sphinx_js']\n   ```\n\n5. If you want to document TypeScript, add:\n\n   ```python\n   js_language = 'typescript'\n   ```\n\n   to `conf.py` as well.\n\n6. If your JS source code is anywhere but at the root of your project, add:\n\n   ```python\n   js_source_path = '../somewhere/else'\n   ```\n\n   on a line by itself in `conf.py`. The root of your JS source tree should be\n   where that setting points, relative to the `conf.py` file.\n\n   The default, `../`, works well when there is a `docs` folder at the root\n   of your project and your source code lives directly inside the root.\n\n7. If you have special JSDoc or TypeDoc configuration, add:\n\n   ```python\n   jsdoc_config_path = '../conf.json'\n   ```\n\n   to `conf.py` as well.\n\n8. If you're documenting only JS or TS and no other languages (like C), you can\n   set your \"primary domain\" to JS in `conf.py`:\n\n   ```python\n   primary_domain = 'js'\n   ```\n\n   The domain is `js` even if you're writing TypeScript. Then you can omit\n   all the \"js:\" prefixes in the directives below.\n\n## History\n\nsphinx-js was created in 2017 by Erik Rose at Mozilla. It was transferred from\nMozilla to the Pyodide organization in 2025.\n\n## Use\n\nIn short, in a Sphinx project, use the following directives to pull in your\nJSDoc documentation, then tell Sphinx to render it all by running `make html`\nin your docs directory. If you have never used Sphinx or written\nreStructuredText before, here is [where we left off in its tutorial](https://www.sphinx-doc.org/en/stable/tutorial.html#defining-document-structure).\nFor a quick start, just add things to index.rst until you prove things are\nworking.\n\n### autofunction\n\nFirst, document your JS code using standard JSDoc formatting:\n\n```javascript\n/**\n * Return the ratio of the inline text length of the links in an element to\n * the inline text length of the entire element.\n *\n * @param {Node} node - Types or not: either works.\n * @throws {PartyError|Hearty} Multiple types work fine.\n * @returns {Number} Types and descriptions are both supported.\n */\nfunction linkDensity(node) {\n  const length = node.flavors.get(\"paragraphish\").inlineLength;\n  const lengthWithoutLinks = inlineTextLength(\n    node.element,\n    (element) => element.tagName !== \"A\",\n  );\n  return (length - lengthWithoutLinks) / length;\n}\n```\n\nThen, reference your documentation using sphinx-js directives. Our directives\nwork much like Sphinx's standard autodoc ones. You can specify just a\nfunction's name:\n\n```rst\n.. js:autofunction:: someFunction\n```\n\nand a nicely formatted block of documentation will show up in your docs.\n\nYou can also throw in your own explicit parameter list, if you want to note\noptional parameters:\n\n```rst\n.. js:autofunction:: someFunction(foo, bar[, baz])\n```\n\nParameter properties and destructuring parameters also work fine, using\n[standard JSDoc syntax](https://jsdoc.app/tags-param.html#parameters-with-properties):\n\n```javascript\n/**\n * Export an image from the given canvas and save it to the disk.\n *\n * @param {Object} options Output options\n * @param {string} options.format The output format (``jpeg``,  ``png``, or\n *     ``webp``)\n * @param {number} options.quality The output quality when format is\n *     ``jpeg`` or ``webp`` (from ``0.00`` to ``1.00``)\n */\nfunction saveCanvas({ format, quality }) {\n  // ...\n}\n```\n\nExtraction of default parameter values works as well. These act as expected,\nwith a few caveats:\n\n```javascript\n/**\n * You must declare the params, even if you have nothing else to say, so\n * JSDoc will extract the default values.\n *\n * @param [num]\n * @param [str]\n * @param [bool]\n * @param [nil]\n */\nfunction defaultsDocumentedInCode(\n  num = 5,\n  str = \"true\",\n  bool = true,\n  nil = null,\n) {}\n\n/**\n * JSDoc guesses types for things like \"42\". If you have a string-typed\n * default value that looks like a number or boolean, you'll need to\n * specify its type explicitly. Conversely, if you have a more complex\n * value like an arrow function, specify a non-string type on it so it\n * isn't interpreted as a string. Finally, if you have a disjoint type like\n * {string|Array} specify string first if you want your default to be\n * interpreted as a string.\n *\n * @param {function} [func=() => 5]\n * @param [str=some string]\n * @param {string} [strNum=42]\n * @param {string|Array} [strBool=true]\n * @param [num=5]\n * @param [nil=null]\n */\nfunction defaultsDocumentedInDoclet(func, strNum, strBool, num, nil) {}\n```\n\nYou can even add additional content. If you do, it will appear just below any\nextracted documentation:\n\n```rst\n.. js:autofunction:: someFunction\n\n    Here are some things that will appear...\n\n    * Below\n    * The\n    * Extracted\n    * Docs\n\n    Enjoy!\n```\n\n`js:autofunction` has one option, `:short-name:`, which comes in handy for\nchained APIs whose implementation details you want to keep out of sight. When\nyou use it on a class method, the containing class won't be mentioned in the\ndocs, the function will appear under its short name in indices, and cross\nreferences must use the short name as well (`:func:`someFunction``):\n\n```rst\n.. js:autofunction:: someClass#someFunction\n   :short-name:\n```\n\n`autofunction` can also be used on callbacks defined with the [@callback tag](https://jsdoc.app/tags-callback.html).\n\nThere is experimental support for abusing `autofunction` to document\n[@typedef tags](https://jsdoc.app/tags-typedef.html) as well, though the\nresult will be styled as a function, and `@property` tags will fall\nmisleadingly under an \"Arguments\" heading. Still, it's better than nothing\nuntil we can do it properly.\n\nIf you are using typedoc, it also is possible to destructure keyword arguments\nby using the `@destructure` tag:\n\n```typescript\n/**\n* @param options\n* @destructure options\n*/\nfunction f({x , y } : {\n    /** The x value */\n    x : number,\n    /** The y value */\n    y : string\n}){ ... }\n```\n\nwill be documented like:\n\n```\noptions.x (number) The x value\noptions.y (number) The y value\n```\n\n### autoclass\n\nWe provide a `js:autoclass` directive which documents a class with the\nconcatenation of its class comment and its constructor comment. It shares all\nthe features of `js:autofunction` and even takes the same `:short-name:`\nflag, which can come in handy for inner classes. The easiest way to use it is\nto invoke its `:members:` option, which automatically documents all your\nclass's public methods and attributes:\n\n```rst\n.. js:autoclass:: SomeEs6Class(constructor, args, if, you[, wish])\n   :members:\n```\n\nYou can add private members by saying:\n\n```rst\n.. js:autoclass:: SomeEs6Class\n   :members:\n   :private-members:\n```\n\nPrivacy is determined by JSDoc `@private` tags or TypeScript's `private`\nkeyword.\n\nExclude certain members by name with `:exclude-members:`:\n\n```rst\n.. js:autoclass:: SomeEs6Class\n   :members:\n   :exclude-members: Foo, bar, baz\n```\n\nOr explicitly list the members you want. We will respect your ordering.\n\n```rst\n.. js:autoclass:: SomeEs6Class\n   :members: Qux, qum\n```\n\nWhen explicitly listing members, you can include `*` to include all\nunmentioned members. This is useful to have control over ordering of some\nelements, without having to include an exhaustive list.\n\n```rst\n.. js:autoclass:: SomeEs6Class\n   :members: importMethod, *, uncommonlyUsedMethod\n```\n\nFinally, if you want full control, pull your class members in one at a time by\nembedding `js:autofunction` or `js:autoattribute`:\n\n```rst\n.. js:autoclass:: SomeEs6Class\n\n   .. js:autofunction:: SomeEs6Class#someMethod\n\n   Additional content can go here and appears below the in-code comments,\n   allowing you to intersperse long prose passages and examples that you\n   don't want in your code.\n```\n\n### autoattribute\n\nThis is useful for documenting public properties:\n\n```javascript\nclass Fnode {\n  constructor(element) {\n    /**\n     * The raw DOM element this wrapper describes\n     */\n    this.element = element;\n  }\n}\n```\n\nAnd then, in the docs:\n\n```rst\n.. autoclass:: Fnode\n\n   .. autoattribute:: Fnode#element\n```\n\nThis is also the way to document ES6-style getters and setters, as it omits the\ntrailing `()` of a function. The assumed practice is the usual JSDoc one:\ndocument only one of your getter/setter pair:\n\n```javascript\nclass Bing {\n  /** The bong of the bing */\n  get bong() {\n    return this._bong;\n  }\n\n  set bong(newBong) {\n    this._bong = newBong * 2;\n  }\n}\n```\n\nAnd then, in the docs:\n\n```rst\n.. autoattribute:: Bing#bong\n```\n\n### automodule\n\nThis directive documents all exports on a module. For example:\n\n```rst\n.. js:automodule:: package.submodule\n```\n\n### autosummary\n\nThis directive should be paired with an automodule directive (which may occur in\na distinct rst file). It makes a summary table with links to the entries\ngenerated by the automodule directive. Usage:\n\n```rst\n.. js:automodule:: package.submodule\n```\n\n## Dodging Ambiguity With Pathnames\n\nIf you have same-named objects in different files, use pathnames to\ndisambiguate them. Here's a particularly long example:\n\n```rst\n.. js:autofunction:: ./some/dir/some/file.SomeClass#someInstanceMethod.staticMethod~innerMember\n```\n\nYou may recognize the separators `#.~` from [JSDoc namepaths](https://jsdoc.app/about-namepaths.html); they work the same here.\n\nFor conciseness, you can use any unique suffix, as long as it consists of\ncomplete path segments. These would all be equivalent to the above, assuming\nthey are unique within your source tree:\n\n```\ninnerMember\nstaticMethod~innerMember\nSomeClass#someInstanceMethod.staticMethod~innerMember\nsome/file.SomeClass#someInstanceMethod.staticMethod~innerMember\n```\n\nThings to note:\n\n- We use simple file paths rather than JSDoc's `module:` prefix or TypeDoc's\n  `external:` or `module:` ones.\n- We use simple backslash escaping exclusively rather than switching escaping\n  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\n  be escaped are `#.~(/`, though you do not need to escape the dots in a\n  leading `./` or `../`. A really horrible path might be:\n\n  ```\n  some/path\\ with\\ spaces/file.topLevelObject#instanceMember.staticMember\\(with\\(parens\n  ```\n\n- Relative paths are relative to the `js_source_path` specified in the\n  config. Absolute paths are not allowed.\n\nBehind the scenes, sphinx-js will change all separators to dots so that:\n\n- Sphinx's \"shortening\" syntax works: \":func:\\`~InwardRhs.atMost\\`\" prints as\n  merely`atMost()`. (For now, you should always use dots rather than other\n  namepath separators: `#~`.)\n- Sphinx indexes more informatively, saying methods belong to their classes.\n\n## Saving Keystrokes By Setting The Primary Domain\n\nTo save some keystrokes, you can set:\n\n```python\nprimary_domain = 'js'\n```\n\nin `conf.py` and then use `autofunction` rather than `js:autofunction`.\n\n## TypeScript: Getting Superclass and Interface Links To Work\n\nTo have a class link to its superclasses and implemented interfaces, you'll\nneed to document the superclass (or interface) somewhere using `js:autoclass`\nor `js:class` and use the class's full (but dotted) path when you do:\n\n```rst\n.. js:autoclass:: someFile.SomeClass\n```\n\nUnfortunately, Sphinx's `~` syntax doesn't work in these spots, so users will\nsee the full paths in the documentation.\n\n## TypeScript: Cross references\n\nTypeScript types will be converted to cross references. To render cross\nreferences, you can define a hook in `conf.py` called `ts_type_xref_formatter`. It\nshould take two arguments: the first argument is the sphinx confix, and the\nsecond is an `sphinx_js.ir.TypeXRef` object. This has a `name` field and two\nvariants:\n\n- a `sphinx_js.ir.TypeXRefInternal` with fields `path` and `kind`\n- a `sphinx_js.ir.TypeXRefExternal` with fields `name`, `package`,\n  `sourcefilename` and `qualifiedName`\n\nThe return value should be restructured text that you wish to be inserted in\nplace of the type. For example:\n\n```python\ndef ts_xref_formatter(config, xref):\n    if isinstance(xref, TypeXRefInternal):\n        name = rst.escape(xref.name)\n        return f\":js:{xref.kind}:`{name}`\"\n    else:\n        # Otherwise, don't insert a xref\n        return xref.name\n```\n\n## Configuration Reference\n\n### `js_language`\n\nUse 'javascript' or 'typescript' depending on the language you use. The\ndefault is 'javascript'.\n\n### `js_source_path`\n\nA list of directories to scan (non-recursively) for JS or TS source files,\nrelative to Sphinx's conf.py file. Can be a string instead if there is only\none. If there is more than one, `root_for_relative_js_paths` must be\nspecified as well. Defaults to `../`.\n\n### `root_for_relative_js_paths`\n\nRelative JS entity paths are resolved relative to this path. Defaults to\n`js_source_path` if not present.\n\n### `jsdoc_config_path`\n\nA conf.py-relative path to a JSDoc config file, which is useful if you want to\nspecify your own JSDoc options, like recursion and custom filename matching.\nIf using TypeDoc, you can also point to a `typedoc.json` file.\n\n### `jsdoc_tsconfig_path`\n\nIf using TypeDoc, specify the path of `tsconfig.json` file\n\n### `ts_type_xref_formatter`\n\nA function for formatting TypeScript type cross references. See the\n\"TypeScript: Cross references\" section below.\n\n### `ts_type_bold`\n\nMake all TypeScript types bold if `true`.\n\n### `ts_sphinx_js_config`\n\nA link to a TypeScript config file.\n\n## The `ts_sphinx_js_config` file\n\nThis file should be a TypeScript module. It's executed in a context where it can\nimport `typedoc` and `sphinx_js`. These functions take TypeDoc IR objects as\narguments. Since the TypeDoc IR is unstable, this config may often break when\nswitching TypeDoc versions. However, these hooks are very powerful so using them\nmay be worthwhile anyways. This API is experimental and may change in the\nfuture.\n\nFor an example, you can see Pyodide's config file [here](shouldDestructureArg).\n\nThis file should export a config object with some of the three following\nfunctions:\n\n- `shouldDestructureArg: (param: ParameterReflection) => boolean`\n\nThis function takes a `ParameterReflection` and decides if it should be\ndestructured. If so, it's equivalent to putting a `@destructure` tag for the\nargument. For example:\n\n```typescript\nfunction shouldDestructureArg(param: ParameterReflection) {\n  return param.name === \"options\";\n}\n```\n\n- `preConvert?: (app: Application) => Promise<void>;`\n\nThis hook is called with the TypeDoc application as argument before the\nTypeScript files are parsed. For example, it can be used to add extra TypeDoc\nplugins.\n\n- `postConvert: (app: Application, project: ProjectReflection, typeDocToIRMap: Map<DeclarationReflection, TopLevelIR>) => void`\n\nThis hook is called after the sphinx_js IR is created. It can be used to\nmodify the IR arbitrarily. It is very experimental and subject to breaking\nchanges.\n\nFor example, this `postConvert` hook removes the constructor from classes marked with\n`@hideconstructor`.\n\n```typescript\nfunction postConvert(app, project, typeDocToIRMap) {\n  for (const [key, value] of typeDocToIRMap.entries()) {\n    if (\n      value.kind === \"class\" &&\n      value.modifier_tags.includes(\"@hideconstructor\")\n    ) {\n      value.constructor_ = null;\n    }\n  }\n}\n```\n\nTo use it, you'll also need to add a tag definition for `@hideconstructor` to your `tsdoc.json` file:\n\n```json\n{\n  \"tagDefinitions\": [\n    {\n      \"tagName\": \"@hideconstructor\",\n      \"syntaxKind\": \"modifier\"\n    }\n  ]\n}\n```\n\nThis `postConvert` hook hides external attributes and functions from the documentation:\n\n```typescript\nfunction postConvert(app, project, typeDocToIRMap) {\n  for (const [key, value] of typeDocToIRMap.entries()) {\n    if (value.kind === \"attribute\" || value.kind === \"function\") {\n      value.is_private = key.flags.isExternal || key.flags.isPrivate;\n    }\n  }\n}\n```\n\n## How sphinx-js finds typedoc / jsdoc\n\n1. If the environment variable `SPHINX_JS_NODE_MODULES` is defined, it is\n   expected to point to a `node_modules` folder in which typedoc / jsdoc is installed.\n\n2. If `SPHINX_JS_NODE_MODULES` is not defined, we look in the directory of\n   `conf.py` for a `node_modules` folder in which typedoc / jsdoc. If this is\n   not found, we look for a `node_modules` folder in the parent directories\n   until we make it to the root of the file system.\n\n3. We check if `typedoc` / `jsdoc` are on the PATH, if so we use that.\n\n4. If none of the previous approaches located `typedoc` / `jsdoc` we raise an error.\n\n## Example\n\nA good example using most of sphinx-js's functionality is the Fathom\ndocumentation. A particularly juicy page is\n<https://mozilla.github.io/fathom/ruleset.html>. Click the \"View page\nsource\" link to see the raw directives.\n\nFor a TypeScript example, see [the Pyodide api docs](https://pyodide.org/en/stable/usage/api/js-api.html).\n\n[ReadTheDocs](https://readthedocs.org/) is the canonical hosting platform for\nSphinx docs and now supports sphinx-js. Put this in the\n`.readthedocs.yml` file at the root of your repo:\n\n```yaml\npython:\n  install:\n    - requirements: docs/requirements.txt\n```\n\nThen put the version of sphinx-js you want in `docs/requirements.txt`. For\nexample:\n\n```\nsphinx-js==3.1.2\n```\n\n## Caveats\n\n- We don't understand the inline JSDoc constructs like `{@link foo}`; you\n  have to use Sphinx-style equivalents for now, like `:js:func:`foo`` (or\nsimply `:func:`foo`` if you have set `primary_domain = 'js'` in conf.py.\n- So far, we understand and convert the JSDoc block tags `@param`,\n  `@returns`, `@throws`, `@example` (without the optional `<caption>`),\n  `@deprecated`, `@see`, and their synonyms. Other ones will go _poof_ into\n  the ether.\n\n## Tests\n\nRun the tests using nox, which will also install JSDoc and TypeDoc at pinned\nversions:\n\n```bash\npip install nox\nnox\n```\n\n## Provenance\n\nsphinx-js was originally written and maintained by Erik Rose and various\ncontributors within and without the Mozilla Corporation and Foundation.\nSee `CONTRIBUTORS` for details.\n"
  },
  {
    "path": "biome.json",
    "content": "{\n\t\"$schema\": \"https://biomejs.dev/schemas/2.1.1/schema.json\",\n\t\"vcs\": { \"enabled\": false, \"clientKind\": \"git\", \"useIgnoreFile\": false },\n\t\"files\": {\n\t\t\"includes\": [\n\t\t\t\"**/*\",\n\t\t\t\"!**/build/**\",\n\t\t\t\"!**/.mypy_cache/**\",\n\t\t\t\"!**/.vscode/**\"\n\t\t]\n\t},\n\t\"formatter\": {\n\t\t\"enabled\": true,\n\t\t\"formatWithErrors\": false,\n\t\t\"indentStyle\": \"space\",\n\t\t\"indentWidth\": 2,\n\t\t\"lineEnding\": \"lf\",\n\t\t\"lineWidth\": 80,\n\t\t\"attributePosition\": \"auto\",\n\t\t\"bracketSameLine\": false,\n\t\t\"bracketSpacing\": true,\n\t\t\"expand\": \"auto\",\n\t\t\"useEditorconfig\": true\n\t},\n\t\"linter\": { \"enabled\": false, \"rules\": { \"recommended\": true } },\n\t\"javascript\": {\n\t\t\"formatter\": {\n\t\t\t\"quoteProperties\": \"asNeeded\",\n\t\t\t\"trailingCommas\": \"all\",\n\t\t\t\"semicolons\": \"always\",\n\t\t\t\"arrowParentheses\": \"always\",\n\t\t\t\"bracketSameLine\": false,\n\t\t\t\"quoteStyle\": \"double\",\n\t\t\t\"attributePosition\": \"auto\",\n\t\t\t\"bracketSpacing\": true\n\t\t}\n\t},\n\t\"html\": { \"formatter\": { \"selfCloseVoidElements\": \"always\" } },\n\t\"assist\": {\n\t\t\"enabled\": false,\n\t\t\"actions\": { \"source\": { \"organizeImports\": \"on\" } }\n\t}\n}\n"
  },
  {
    "path": "noxfile.py",
    "content": "from pathlib import Path\nfrom textwrap import dedent\n\nimport nox\nfrom nox.sessions import Session\n\nPROJECT_ROOT = Path(__file__).parent\n\n\n@nox.session(python=[\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"])\ndef tests(session: Session) -> None:\n    session.install(\".[test]\")\n    venvroot = Path(session.bin).parent\n    (venvroot / \"node_modules\").mkdir()\n    with session.chdir(venvroot):\n        session.run(\n            \"npm\", \"i\", \"--no-save\", \"jsdoc@4.0.0\", \"typedoc@0.25\", external=True\n        )\n    session.run(\n        \"pytest\",\n        \"--junitxml=test-results.xml\",\n        \"--cov=sphinx_js\",\n        \"--cov-report\",\n        \"xml\",\n    )\n\n\ndef typecheck_ts(session: Session, typedoc: str) -> None:\n    if typedoc == \"0.26\":\n        # Upstream type errors here =(\n        return\n    # Typecheck\n    with session.chdir(\"sphinx_js/js\"):\n        session.run(\"npm\", \"i\", f\"typedoc@{typedoc}\", external=True)\n        session.run(\"npm\", \"i\", external=True)\n        session.run(\"npx\", \"tsc\", external=True)\n\n\n@nox.session(python=[\"3.12\"])\n@nox.parametrize(\"typedoc\", [\"0.25\", \"0.26\", \"0.27\", \"0.28\"])\ndef test_typedoc(session: Session, typedoc: str) -> None:\n    typecheck_ts(session, typedoc)\n    # Install python dependencies\n    session.install(\".[test]\")\n\n    venvroot = Path(session.bin).parent\n    node_modules = (venvroot / \"node_modules\").resolve()\n    node_modules.mkdir()\n    with session.chdir(venvroot):\n        # Install node dependencies\n        session.run(\n            \"npm\",\n            \"i\",\n            \"--no-save\",\n            \"tsx\",\n            \"jsdoc@4.0.0\",\n            f\"typedoc@{typedoc}\",\n            external=True,\n        )\n        # Run typescript tests\n        test_file = (PROJECT_ROOT / \"tests/test.ts\").resolve()\n        register_import_hook = PROJECT_ROOT / \"sphinx_js/js/registerImportHook.mjs\"\n        ts_tests = Path(venvroot / \"ts_tests\")\n        # Write script to a file so that it is easy to rerun without reinstalling dependencies.\n        ts_tests.write_text(\n            dedent(\n                f\"\"\"\\\n                #!/bin/sh\n                npx typedoc --version\n                TYPEDOC_NODE_MODULES={venvroot} node --import {register_import_hook} --import {node_modules / \"tsx/dist/loader.mjs\"} --test {test_file}\n                \"\"\"\n            )\n        )\n        ts_tests.chmod(0o777)\n        session.run(ts_tests, external=True)\n\n    # Run Python tests\n    session.run(\"pytest\", \"--junitxml=test-results.xml\", \"-k\", \"not js\")\n\n\n@nox.session(python=[\"3.12\"])\ndef test_sphinx_6(session: Session) -> None:\n    session.install(\"sphinx<7\")\n    session.install(\".[test]\")\n    venvroot = Path(session.bin).parent\n    (venvroot / \"node_modules\").mkdir()\n    with session.chdir(venvroot):\n        session.run(\n            \"npm\", \"i\", \"--no-save\", \"jsdoc@4.0.0\", \"typedoc@0.25\", external=True\n        )\n    session.run(\"pytest\", \"--junitxml=test-results.xml\", \"-k\", \"not js\")\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"hatchling\", \"hatch-vcs\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"sphinx-js\"\ndescription = \"Support for using Sphinx on JSDoc-documented JS code\"\nreadme = \"README.md\"\nlicense = {text = \"MIT\"}\nauthors = [\n    {name = \"Hood Chatham\", email = \"roberthoodchatham@gmail.com\"},\n]\nrequires-python = \">=3.10\"\ndependencies = [\n    \"attrs\",\n    \"cattrs<25.1\",\n    \"Jinja2>2.0\",\n    \"markupsafe\",\n    \"parsimonious>=0.10.0,<0.11.0\",\n    \"Sphinx>=4.1.0\",\n]\nkeywords = [\n    \"docs\",\n    \"documentation\",\n    \"javascript\",\n    \"js\",\n    \"jsdoc\",\n    \"restructured\",\n    \"sphinx\",\n    \"typedoc\",\n    \"typescript\",\n]\nclassifiers = [\n    \"Framework :: Sphinx :: Domain\",\n    \"Framework :: Sphinx :: Extension\",\n    \"Intended Audience :: Developers\",\n    \"Natural Language :: English\",\n    \"Development Status :: 5 - Production/Stable\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Programming Language :: Python :: 3\",\n    \"Topic :: Documentation :: Sphinx\",\n    \"Topic :: Software Development :: Documentation\",\n    \"Typing :: Typed\",\n]\ndynamic = [\"version\"]\n\n[project.urls]\nHomepage = \"https://github.com/pyodide/sphinx-js\"\n\n[project.optional-dependencies]\ntest = [\n    \"beautifulsoup4\",\n    \"build\",\n    \"defusedxml\",\n    \"nox\",\n    \"pytest-cov\",\n    \"recommonmark\",\n    \"twine\",\n]\n\n[tool.hatch.version]\nsource = \"vcs\"\n\n[tool.hatch.build.targets.sdist]\ninclude = [\n    \"/sphinx_js\",\n    \"/tests\",\n    \"/LICENSE\",\n    \"/requirements_dev.txt\",\n    \"/noxfile.py\",\n    \"/README.md\",\n]\n\n[tool.hatch.build.targets.wheel]\npackages = [\"sphinx_js\"]\n\n[tool.mypy]\npython_version = \"3.10\"\nshow_error_codes = true\nwarn_unreachable = true\nenable_error_code = [\"ignore-without-code\", \"redundant-expr\", \"truthy-bool\"]\n\n# Strict checks\nwarn_unused_configs = true\ncheck_untyped_defs = true\ndisallow_any_generics = true\ndisallow_subclassing_any = true\ndisallow_untyped_calls = true\ndisallow_untyped_defs = true\ndisallow_incomplete_defs = true\ndisallow_untyped_decorators = true\nno_implicit_optional = true\nwarn_redundant_casts = true\nwarn_unused_ignores = true\nwarn_return_any = true\nno_implicit_reexport = true\nstrict_equality = true\n\n[[tool.mypy.overrides]]\nmodule = \"sphinx_js.parsers\"\ndisallow_untyped_defs = false\ndisallow_untyped_calls = false\n\n\n[tool.ruff]\nlint.select = [\n  \"E\",     # pycodestyles\n  \"W\",     # pycodestyles\n  \"F\",     # pyflakes\n  \"B0\",    # bugbear (all B0* checks enabled by default)\n  \"B904\",  # bugbear (Within an except clause, raise exceptions with raise ... from err)\n  \"B905\",  # bugbear (zip() without an explicit strict= parameter set.)\n  \"UP\",    # pyupgrade\n  \"I\",     # isort\n  \"PGH\",   # pygrep-hooks\n]\nlint.ignore = [\"E402\", \"E501\", \"E731\", \"E741\", \"B904\", \"B020\", \"UP031\"]\ntarget-version = \"py310\"\n"
  },
  {
    "path": "sphinx_js/__init__.py",
    "content": "from os.path import join, normpath\nfrom pathlib import Path\nfrom textwrap import dedent\nfrom typing import Any\n\nfrom sphinx.application import Sphinx\nfrom sphinx.errors import SphinxError\n\nfrom .directives import (\n    add_directives,\n)\nfrom .jsdoc import Analyzer as JsAnalyzer\nfrom .typedoc import Analyzer as TsAnalyzer\n\nSPHINX_JS_CSS = \"sphinx_js.css\"\n\n\ndef make_css_file(app: Sphinx) -> None:\n    dst = Path(app.outdir) / \"_static\" / SPHINX_JS_CSS\n    text = \"\"\n    if app.config.ts_type_bold:\n        text = dedent(\n            \"\"\"\\\n            .sphinx_js-type {\n                font-weight: bolder;\n            }\n            \"\"\"\n        )\n    dst.write_text(text)\n\n\ndef on_build_finished(app: Sphinx, exc: Exception | None) -> None:\n    if exc or app.builder.format != \"html\":\n        return\n    make_css_file(app)\n\n\ndef setup(app: Sphinx) -> None:\n    app.setup_extension(\"sphinx.ext.autosummary\")\n\n    # I believe this is the best place to run jsdoc. I was tempted to use\n    # app.add_source_parser(), but I think the kind of source it's referring to\n    # is RSTs.\n    app.connect(\"builder-inited\", analyze)\n\n    add_directives(app)\n\n    # TODO: We could add a js:module with app.add_directive_to_domain().\n\n    app.add_config_value(\"js_language\", default=\"javascript\", rebuild=\"env\")\n    app.add_config_value(\n        \"js_source_path\", default=[\"../\"], rebuild=\"env\", types=[str, list]\n    )\n\n    # We could use a callable as the \"default\" param here, but then we would\n    # have had to duplicate or build framework around the logic that promotes\n    # js_source_path to a list and calls abspath() on it. It's simpler this way\n    # until we need to access js_source_path from more than one place.\n    app.add_config_value(\"root_for_relative_js_paths\", None, \"env\")\n\n    app.add_config_value(\"jsdoc_config_path\", default=None, rebuild=\"env\")\n    app.add_config_value(\"jsdoc_tsconfig_path\", default=None, rebuild=\"env\")\n\n    app.add_config_value(\"ts_type_xref_formatter\", None, \"env\")\n    app.add_config_value(\"ts_type_bold\", False, \"env\")\n    app.add_config_value(\"ts_sphinx_js_config\", None, \"env\")\n\n    app.add_css_file(SPHINX_JS_CSS)\n    app.connect(\"build-finished\", on_build_finished)\n\n\ndef analyze(app: Sphinx) -> None:\n    \"\"\"Run JSDoc or another analysis tool across a whole codebase, and squirrel\n    away its results in a language-specific Analyzer.\"\"\"\n    # Normalize config values:\n    source_paths = (\n        [app.config.js_source_path]\n        if isinstance(app.config.js_source_path, str)\n        else app.config.js_source_path\n    )\n    abs_source_paths = [normpath(join(app.confdir, path)) for path in source_paths]\n    root_for_relative_paths = root_or_fallback(\n        normpath(join(app.confdir, app.config.root_for_relative_js_paths))\n        if app.config.root_for_relative_js_paths\n        else None,\n        abs_source_paths,\n    )\n\n    # Pick analyzer:\n    try:\n        analyzer: Any = {\"javascript\": JsAnalyzer, \"typescript\": TsAnalyzer}[\n            app.config.js_language\n        ]\n    except KeyError:\n        raise SphinxError(\n            \"Unsupported value of js_language in config: %s\" % app.config.js_language\n        )\n\n    # Analyze source code:\n    app._sphinxjs_analyzer = analyzer.from_disk(  # type:ignore[attr-defined]\n        abs_source_paths, app, root_for_relative_paths\n    )\n\n\ndef root_or_fallback(\n    root_for_relative_paths: str | None, abs_source_paths: list[str]\n) -> str:\n    \"\"\"Return the path that relative JS entity paths in the docs are relative to.\n\n    Fall back to the sole JS source path if the setting is unspecified.\n\n    :arg root_for_relative_paths: The absolute-ized root_for_relative_js_paths\n        setting. None if the user hasn't specified it.\n    :arg abs_source_paths: Absolute paths of dirs to scan for JS code\n\n    \"\"\"\n    if root_for_relative_paths:\n        return root_for_relative_paths\n    else:\n        if len(abs_source_paths) > 1:\n            raise SphinxError(\n                \"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.\"\n            )\n        else:\n            return abs_source_paths[0]\n"
  },
  {
    "path": "sphinx_js/analyzer_utils.py",
    "content": "\"\"\"Conveniences shared among analyzers\"\"\"\n\nimport os\nimport shutil\nfrom collections.abc import Callable, Sequence\nfrom functools import cache, wraps\nfrom json import dump, load\nfrom pathlib import Path\nfrom typing import Any, ParamSpec, TypeVar\n\nfrom sphinx.errors import SphinxError\n\n\ndef program_name_on_this_platform(program: str) -> str:\n    \"\"\"Return the name of the executable file on the current platform, given a\n    command name with no extension.\"\"\"\n    return program + \".cmd\" if os.name == \"nt\" else program\n\n\n@cache\ndef search_node_modules(cmdname: str, cmdpath: str, dir: str | Path) -> str:\n    if \"SPHINX_JS_NODE_MODULES\" in os.environ:\n        return str(Path(os.environ[\"SPHINX_JS_NODE_MODULES\"]) / cmdpath)\n\n    # We want to include \"curdir\" in parent_dirs, so add a garbage suffix\n    parent_dirs = (Path(dir) / \"garbage\").parents\n\n    # search for local install\n    for base in parent_dirs:\n        typedoc = base / \"node_modules\" / cmdpath\n        if typedoc.is_file():\n            return str(typedoc.resolve())\n\n    # perhaps it's globally installed\n    result = shutil.which(cmdname)\n    if result:\n        return str(Path(result).resolve())\n\n    raise SphinxError(\n        f'{cmdname} was not found. Install it using \"npm install {cmdname}\".'\n    )\n\n\nclass Command:\n    def __init__(self, program: str):\n        self.program = program_name_on_this_platform(program)\n        self.args: list[str] = []\n\n    def add(self, *args: str) -> None:\n        self.args.extend(args)\n\n    def make(self) -> list[str]:\n        return [self.program] + self.args\n\n\nT = TypeVar(\"T\")\nP = ParamSpec(\"P\")\n\n\ndef cache_to_file(\n    get_filename: Callable[..., str | None],\n) -> Callable[[Callable[P, T]], Callable[P, T]]:\n    \"\"\"Return a decorator that will cache the result of ``get_filename()`` to a\n    file\n\n    :arg get_filename: A function which receives the original arguments of the\n        decorated function\n    \"\"\"\n\n    def decorator(fn: Callable[P, T]) -> Callable[P, T]:\n        @wraps(fn)\n        def decorated(*args: P.args, **kwargs: P.kwargs) -> Any:\n            filename = get_filename(*args, **kwargs)\n            if filename and os.path.isfile(filename):\n                with open(filename, encoding=\"utf-8\") as f:\n                    return load(f)\n            res = fn(*args, **kwargs)\n            if filename:\n                with open(filename, \"w\", encoding=\"utf-8\") as f:\n                    dump(res, f, indent=2)\n            return res\n\n        return decorated\n\n    return decorator\n\n\ndef is_explicitly_rooted(path: str) -> bool:\n    \"\"\"Return whether a relative path is explicitly rooted relative to the\n    cwd, rather than starting off immediately with a file or folder name.\n\n    It's nice to have paths start with \"./\" (or \"../\", \"../../\", etc.) so, if a\n    user is that explicit, we still find the path in the suffix tree.\n\n    \"\"\"\n    return path.startswith((\"../\", \"./\")) or path in (\"..\", \".\")\n\n\ndef dotted_path(segments: Sequence[str]) -> str:\n    \"\"\"Convert a JS object path (``['dir/', 'file/', 'class#',\n    'instanceMethod']``) to a dotted style that Sphinx will better index.\n\n    Strip off any leading relative-dir segments (./ or ../) because they lead\n    to invalid paths like \".....foo\". Ultimately, we should thread ``base_dir``\n    into this and construct a full path based on that.\n\n    \"\"\"\n    if not segments:\n        return \"\"\n    segments_without_separators = [\n        s[:-1] for s in segments[:-1] if s not in [\"./\", \"../\"]\n    ]\n    segments_without_separators.append(segments[-1])\n    return \".\".join(segments_without_separators)\n"
  },
  {
    "path": "sphinx_js/directives.py",
    "content": "\"\"\"These are the actual Sphinx directives we provide, but they are skeletal.\n\nThe real meat is in their parallel renderer classes, in renderers.py. The split\nis due to the unfortunate trick we need here of having functions return the\ndirective classes after providing them the ``app`` symbol, where we store the\nJSDoc output, via closure. The renderer classes, able to be top-level classes,\ncan access each other and collaborate.\n\n\"\"\"\n\nimport re\nfrom collections.abc import Iterable\nfrom functools import cache\nfrom os.path import join, relpath\nfrom typing import Any, cast\n\nfrom docutils import nodes\nfrom docutils.nodes import Node\nfrom docutils.parsers.rst import Directive\nfrom docutils.parsers.rst import Parser as RstParser\nfrom docutils.parsers.rst.directives import flag\nfrom sphinx import addnodes\nfrom sphinx.addnodes import desc_signature\nfrom sphinx.application import Sphinx\nfrom sphinx.domains import ObjType, javascript\nfrom sphinx.domains.javascript import (\n    JavaScriptDomain,\n    JSCallable,\n    JSConstructor,\n    JSObject,\n    JSXRefRole,\n)\nfrom sphinx.locale import _\nfrom sphinx.util.docfields import GroupedField, TypedField\nfrom sphinx.writers.html5 import HTML5Translator\nfrom sphinx.writers.latex import LaTeXTranslator\nfrom sphinx.writers.text import TextTranslator\n\nfrom .renderers import (\n    AutoAttributeRenderer,\n    AutoClassRenderer,\n    AutoFunctionRenderer,\n    AutoModuleRenderer,\n    AutoSummaryRenderer,\n    Renderer,\n    new_document_from_parent,\n)\n\n\ndef unescape(escaped: str) -> str:\n    # For some reason the string we get has a bunch of null bytes in it??\n    # Remove them...\n    escaped = escaped.replace(\"\\x00\", \"\")\n    # For some reason the extra slash before spaces gets lost between the .rst\n    # source and when this directive is called. So don't replace \"\\<space>\" =>\n    # \"<space>\"\n    return re.sub(r\"\\\\([^ ])\", r\"\\1\", escaped)\n\n\ndef _members_to_exclude(arg: str | None) -> set[str]:\n    \"\"\"Return a set of members to exclude given a comma-delim list of them.\n\n    Exclude none if none are passed. This differs from autodocs' behavior,\n    which excludes all. That seemed useless to me.\n\n    \"\"\"\n    return set(a.strip() for a in (arg or \"\").split(\",\"))\n\n\ndef sphinx_js_type_role(  # type: ignore[no-untyped-def]\n    role,\n    rawtext,\n    text,\n    lineno,\n    inliner,\n    options=None,\n    content=None,\n):\n    \"\"\"\n    The body should be escaped rst. This renders its body as rst and wraps the\n    result in <span class=\"sphinx_js-type\"> </span>\n    \"\"\"\n    unescaped = unescape(text)\n    parent_doc = inliner.document\n    source = parent_doc.get(\"source\", \"\")\n    # Get line number stored by new_document_from_parent in rst_nodes if we can\n    # find it, otherwise use lineno of directive.\n    line = getattr(parent_doc, \"sphinx_js_source_line\", None) or lineno\n    doc = new_document_from_parent(source, parent_doc)\n    # Prepend newlines so errors report correct line number\n    padded = \"\\n\" * (line - 1) + unescaped\n    RstParser().parse(padded, doc)\n    n = nodes.inline(text)\n    n[\"classes\"].append(\"sphinx_js-type\")\n    n += doc.children[0].children\n    return [n], []\n\n\nclass JSXrefMixin:\n    def make_xref(\n        self,\n        rolename: Any,\n        domain: Any,\n        target: Any,\n        innernode: Any = nodes.emphasis,\n        contnode: Any = None,\n        env: Any = None,\n        inliner: Any = None,\n        location: Any = None,\n    ) -> Any:\n        # Set inliner to None just like the PythonXrefMixin does so the\n        # xref doesn't get rendered as a function.\n        return super().make_xref(  # type:ignore[misc]\n            rolename,\n            domain,\n            target,\n            innernode,\n            contnode,\n            env,\n            inliner=None,\n            location=None,\n        )\n\n\nclass JSTypedField(JSXrefMixin, TypedField):\n    pass\n\n\nclass JSGroupedField(JSXrefMixin, GroupedField):\n    pass\n\n\n# Cache this to guarantee it only runs once.\n@cache\ndef fix_js_make_xref() -> None:\n    \"\"\"Monkeypatch to fix sphinx.domains.javascript TypedField and GroupedField\n\n    Fixes https://github.com/sphinx-doc/sphinx/issues/11021\n\n    \"\"\"\n\n    # Replace javascript module\n    javascript.TypedField = JSTypedField  # type:ignore[attr-defined]\n    javascript.GroupedField = JSGroupedField  # type:ignore[attr-defined]\n\n    # Fix the one place TypedField and GroupedField are used in the javascript\n    # module\n    javascript.JSCallable.doc_field_types = [\n        JSTypedField(\n            \"arguments\",\n            label=_(\"Arguments\"),\n            names=(\"argument\", \"arg\", \"parameter\", \"param\"),\n            typerolename=\"func\",\n            typenames=(\"paramtype\", \"type\"),\n        ),\n        JSGroupedField(\n            \"errors\",\n            label=_(\"Throws\"),\n            rolename=\"func\",\n            names=(\"throws\",),\n            can_collapse=True,\n        ),\n    ] + javascript.JSCallable.doc_field_types[2:]\n\n\n# Cache this to guarantee it only runs once.\n@cache\ndef fix_staticfunction_objtype() -> None:\n    \"\"\"Override js:function directive with one that understands static and async\n    prefixes\n    \"\"\"\n\n    JavaScriptDomain.directives[\"function\"] = JSFunction\n\n\n@cache\ndef add_type_param_field_to_directives() -> None:\n    typeparam_field = JSGroupedField(\n        \"typeparam\",\n        label=\"Type parameters\",\n        rolename=\"func\",\n        names=(\"typeparam\",),\n        can_collapse=True,\n    )\n\n    JSCallable.doc_field_types.insert(0, typeparam_field)\n    JSConstructor.doc_field_types.insert(0, typeparam_field)\n\n\nclass JsDirective(Directive):\n    \"\"\"Abstract directive which knows how to pull things out of our IR\"\"\"\n\n    has_content = True\n    required_arguments = 1\n    optional_arguments = 0\n    final_argument_whitespace = True\n\n    option_spec = {\"short-name\": flag}\n\n    def _run(self, renderer_class: type[Renderer], app: Sphinx) -> list[Node]:\n        renderer = renderer_class.from_directive(self, app)\n        note_dependencies(app, renderer.dependencies())\n        return renderer.rst_nodes()\n\n\nclass JsDirectiveWithChildren(JsDirective):\n    option_spec = JsDirective.option_spec.copy()\n    option_spec.update(\n        {\n            \"members\": lambda members: (\n                [m.strip() for m in members.split(\",\")] if members else []\n            ),\n            \"exclude-members\": _members_to_exclude,\n            \"private-members\": flag,\n        }\n    )\n\n\ndef note_dependencies(app: Sphinx, dependencies: Iterable[str]) -> None:\n    \"\"\"Note dependencies of current document.\n\n    :arg app: Sphinx application object\n    :arg dependencies: iterable of filename strings relative to root_for_relative_paths\n    \"\"\"\n    for fn in dependencies:\n        # Dependencies in the IR are relative to `root_for_relative_paths`, itself\n        # relative to the configuration directory.\n        analyzer = app._sphinxjs_analyzer  # type:ignore[attr-defined]\n        abs = join(analyzer._base_dir, fn)\n        # Sphinx dependencies are relative to the source directory.\n        rel = relpath(abs, app.srcdir)\n        app.env.note_dependency(rel)\n\n\ndef auto_function_directive_bound_to_app(app: Sphinx) -> type[Directive]:\n    class AutoFunctionDirective(JsDirective):\n        \"\"\"js:autofunction directive, which spits out a js:function directive\n\n        Takes a single argument which is a JS function name combined with an\n        optional formal parameter list, all mashed together in a single string.\n\n        \"\"\"\n\n        def run(self) -> list[Node]:\n            return self._run(AutoFunctionRenderer, app)\n\n    return AutoFunctionDirective\n\n\ndef auto_class_directive_bound_to_app(app: Sphinx) -> type[Directive]:\n    class AutoClassDirective(JsDirectiveWithChildren):\n        \"\"\"js:autoclass directive, which spits out a js:class directive\n\n        Takes a single argument which is a JS class name combined with an\n        optional formal parameter list for the constructor, all mashed together\n        in a single string.\n\n        \"\"\"\n\n        def run(self) -> list[Node]:\n            return self._run(AutoClassRenderer, app)\n\n    return AutoClassDirective\n\n\ndef auto_attribute_directive_bound_to_app(app: Sphinx) -> type[Directive]:\n    class AutoAttributeDirective(JsDirective):\n        \"\"\"js:autoattribute directive, which spits out a js:attribute directive\n\n        Takes a single argument which is a JS attribute name.\n\n        \"\"\"\n\n        def run(self) -> list[Node]:\n            return self._run(AutoAttributeRenderer, app)\n\n    return AutoAttributeDirective\n\n\nclass desc_js_type_parameter_list(nodes.Part, nodes.Inline, nodes.FixedTextElement):\n    \"\"\"Node for a javascript type parameter list.\n\n    Unlike normal parameter lists, we use angle braces <> as the braces. Based\n    on sphinx.addnodes.desc_type_parameter_list\n    \"\"\"\n\n    child_text_separator = \", \"\n\n    def astext(self) -> str:\n        return f\"<{nodes.FixedTextElement.astext(self)}>\"\n\n\ndef html5_visit_desc_js_type_parameter_list(\n    self: HTML5Translator, node: nodes.Element\n) -> None:\n    \"\"\"Define the html/text rendering for desc_js_type_parameter_list. Based on\n    sphinx.writers.html5.visit_desc_type_parameter_list\n    \"\"\"\n    if hasattr(self, \"_visit_sig_parameter_list\"):\n        # Sphinx 7\n        return self._visit_sig_parameter_list(node, addnodes.desc_parameter, \"<\", \">\")\n    # Sphinx <7\n    self.body.append('<span class=\"sig-paren\">&lt;</span>')\n    self.first_param = 1  # type:ignore[attr-defined]\n    self.optional_param_level = 0\n    # How many required parameters are left.\n    self.required_params_left = sum(\n        [isinstance(c, addnodes.desc_parameter) for c in node.children]\n    )\n    self.param_separator = node.child_text_separator\n\n\ndef html5_depart_desc_js_type_parameter_list(\n    self: HTML5Translator, node: nodes.Element\n) -> None:\n    \"\"\"Define the html/text rendering for desc_js_type_parameter_list. Based on\n    sphinx.writers.html5.depart_desc_type_parameter_list\n    \"\"\"\n    if hasattr(self, \"_depart_sig_parameter_list\"):\n        # Sphinx 7\n        return self._depart_sig_parameter_list(node)\n    # Sphinx <7\n    self.body.append('<span class=\"sig-paren\">&gt;</span>')\n\n\ndef text_visit_desc_js_type_parameter_list(\n    self: TextTranslator, node: nodes.Element\n) -> None:\n    if hasattr(self, \"_visit_sig_parameter_list\"):\n        # Sphinx 7\n        return self._visit_sig_parameter_list(node, addnodes.desc_parameter, \"<\", \">\")\n    # Sphinx <7\n    self.add_text(\"<\")\n    self.first_param = 1  # type:ignore[attr-defined]\n\n\ndef text_depart_desc_js_type_parameter_list(\n    self: TextTranslator, node: nodes.Element\n) -> None:\n    if hasattr(self, \"_depart_sig_parameter_list\"):\n        # Sphinx 7\n        return self._depart_sig_parameter_list(node)\n    # Sphinx <7\n    self.add_text(\">\")\n\n\ndef latex_visit_desc_type_parameter_list(\n    self: LaTeXTranslator, node: nodes.Element\n) -> None:\n    pass\n\n\ndef latex_depart_desc_type_parameter_list(\n    self: LaTeXTranslator, node: nodes.Element\n) -> None:\n    pass\n\n\ndef add_param_list_to_signode(signode: desc_signature, params: str) -> None:\n    paramlist = desc_js_type_parameter_list()\n    for arg in params.split(\",\"):\n        paramlist += addnodes.desc_parameter(\"\", \"\", addnodes.desc_sig_name(arg, arg))\n    signode += paramlist\n\n\ndef handle_typeparams_for_signature(\n    self: JSObject, sig: str, signode: desc_signature, *, keep_callsig: bool\n) -> tuple[str, str]:\n    \"\"\"Generic function to handle type params in the sig line for interfaces,\n    classes, and functions.\n\n    For interfaces and classes we don't prefer the look with parentheses so we\n    also remove them (by setting keep_callsig to False).\n    \"\"\"\n    typeparams = None\n    if \"<\" in sig and \">\" in sig:\n        base, _, rest = sig.partition(\"<\")\n        typeparams, _, params = rest.partition(\">\")\n        sig = base + params\n    res = JSCallable.handle_signature(cast(JSCallable, self), sig, signode)\n    sig = sig.strip()\n    lastchild = None\n    # Check for call signature, if present take it off\n    if signode.children[-1].astext().endswith(\")\"):\n        lastchild = signode.children[-1]\n        signode.remove(lastchild)\n    if typeparams:\n        add_param_list_to_signode(signode, typeparams)\n    # if we took off a call signature and we want to keep it put it back.\n    if keep_callsig and lastchild:\n        signode += lastchild\n    return res\n\n\nclass JSFunction(JSCallable):\n    \"\"\"Variant of JSCallable that can take static/async prefixes\"\"\"\n\n    option_spec = {\n        **JSCallable.option_spec,\n        \"static\": flag,\n        \"async\": flag,\n    }\n\n    def get_display_prefix(\n        self,\n    ) -> list[Any]:\n        result = []\n        for name in [\"static\", \"async\"]:\n            if name in self.options:\n                result.extend(\n                    [\n                        addnodes.desc_sig_keyword(name, name),\n                        addnodes.desc_sig_space(),\n                    ]\n                )\n        return result\n\n    def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:\n        return handle_typeparams_for_signature(self, sig, signode, keep_callsig=True)\n\n\nclass JSInterface(JSCallable):\n    \"\"\"An interface directive.\n\n    Based on sphinx.domains.javascript.JSConstructor.\n    \"\"\"\n\n    allow_nesting = True\n\n    def get_display_prefix(self) -> list[Node]:\n        return [\n            addnodes.desc_sig_keyword(\"interface\", \"interface\"),\n            addnodes.desc_sig_space(),\n        ]\n\n    def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:\n        return handle_typeparams_for_signature(self, sig, signode, keep_callsig=False)\n\n\nclass JSTypeAlias(JSObject):\n    doc_field_types = [\n        JSGroupedField(\n            \"typeparam\",\n            label=\"Type parameters\",\n            names=(\"typeparam\",),\n            can_collapse=True,\n        )\n    ]\n\n    def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:\n        return handle_typeparams_for_signature(self, sig, signode, keep_callsig=False)\n\n\nclass JSClass(JSConstructor):\n    def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:\n        return handle_typeparams_for_signature(self, sig, signode, keep_callsig=True)\n\n\n@cache\ndef patch_JsObject_get_index_text() -> None:\n    \"\"\"Add our additional object types to the index\"\"\"\n    orig_get_index_text = JSObject.get_index_text\n\n    def patched_get_index_text(\n        self: JSObject, objectname: str, name_obj: tuple[str, str]\n    ) -> str:\n        name, obj = name_obj\n        if self.objtype == \"interface\":\n            return _(\"%s() (interface)\") % name\n        return orig_get_index_text(self, objectname, name_obj)\n\n    JSObject.get_index_text = patched_get_index_text  # type:ignore[method-assign]\n\n\ndef auto_module_directive_bound_to_app(app: Sphinx) -> type[Directive]:\n    class AutoModuleDirective(JsDirectiveWithChildren):\n        required_arguments = 1\n\n        def run(self) -> list[Node]:\n            return self._run(AutoModuleRenderer, app)\n\n    return AutoModuleDirective\n\n\ndef auto_summary_directive_bound_to_app(app: Sphinx) -> type[Directive]:\n    class JsDocSummary(JsDirective):\n        required_arguments = 1\n\n        def run(self) -> list[Node]:\n            return self._run(AutoSummaryRenderer, app)\n\n    return JsDocSummary\n\n\ndef add_directives(app: Sphinx) -> None:\n    fix_js_make_xref()\n    fix_staticfunction_objtype()\n    add_type_param_field_to_directives()\n    patch_JsObject_get_index_text()\n    app.add_role(\"sphinx_js_type\", sphinx_js_type_role)\n    app.add_directive_to_domain(\n        \"js\", \"autofunction\", auto_function_directive_bound_to_app(app)\n    )\n    app.add_directive_to_domain(\n        \"js\", \"autoclass\", auto_class_directive_bound_to_app(app)\n    )\n    app.add_directive_to_domain(\n        \"js\", \"autoattribute\", auto_attribute_directive_bound_to_app(app)\n    )\n    app.add_directive_to_domain(\n        \"js\", \"automodule\", auto_module_directive_bound_to_app(app)\n    )\n    app.add_directive_to_domain(\n        \"js\", \"autosummary\", auto_summary_directive_bound_to_app(app)\n    )\n    app.add_directive_to_domain(\"js\", \"class\", JSClass)\n    app.add_role_to_domain(\"js\", \"class\", JSXRefRole())\n    JavaScriptDomain.object_types[\"interface\"] = ObjType(_(\"interface\"), \"interface\")\n    app.add_directive_to_domain(\"js\", \"interface\", JSInterface)\n    app.add_role_to_domain(\"js\", \"interface\", JSXRefRole())\n    JavaScriptDomain.object_types[\"typealias\"] = ObjType(_(\"type alias\"), \"typealias\")\n    app.add_directive_to_domain(\"js\", \"typealias\", JSTypeAlias)\n    app.add_role_to_domain(\"js\", \"typealias\", JSXRefRole())\n    app.add_node(\n        desc_js_type_parameter_list,\n        html=(\n            html5_visit_desc_js_type_parameter_list,\n            html5_depart_desc_js_type_parameter_list,\n        ),\n        text=(\n            text_visit_desc_js_type_parameter_list,\n            text_depart_desc_js_type_parameter_list,\n        ),\n        latex=(\n            latex_visit_desc_type_parameter_list,\n            latex_depart_desc_type_parameter_list,\n        ),\n    )\n"
  },
  {
    "path": "sphinx_js/ir.py",
    "content": "\"\"\"Intermediate representation that JS and TypeScript are transformed to for\nuse by the rest of sphinx-js\n\nThis results from my former inability to review any but the most trivial\nTypeScript PRs due to jsdoc's JSON output format being undocumented, often\nsurprising, and occasionally changing.\n\nThis IR is not intended to be a lossless representation of either jsdoc's or\ntypedoc's output. Nor is it meant to generalize to other uses like static\nanalysis. Ideally, it provides the minimum information necessary to render our\nSphinx templates about JS and TS entities. Any expansion or generalization of\nthe IR should be driven by needs of those templates and the (minimal) logic\naround them. The complexity of doing otherwise has no payback.\n\nI was conflicted about introducing an additional representation, since a\nmultiplicity of representations incurs conversion complexity costs at a\nsuperlinear rate. However, I think it is essential complexity here. The\npotentially simpler approach would have been to let the RST template vars\nrequired by our handful of directives be the IR. However, we still would have\nwanted to factor out formatting like the joining of types with \"|\" and the\nunwrapping of comments, making another representation inevitable. Therefore,\nlet's at least have a well-documented one and one slightly more likely to\nsurvive template changes.\n\nThis has to match js/ir.ts\n\"\"\"\n\nfrom collections.abc import Callable, Sequence\nfrom typing import Any, Literal, ParamSpec, TypeVar\n\nimport cattrs\nfrom attrs import Factory, define, field\n\nfrom .analyzer_utils import dotted_path\n\n\n@define\nclass TypeXRefIntrinsic:\n    name: str\n    type: Literal[\"intrinsic\"] = \"intrinsic\"\n\n\n@define\nclass TypeXRefInternal:\n    name: str\n    path: list[str]\n    type: Literal[\"internal\"] = \"internal\"\n    kind: str | None = None\n\n\n@define\nclass TypeXRefExternal:\n    name: str\n    package: str\n    # TODO: use snake case for these like for everything else\n    sourcefilename: str | None\n    qualifiedName: str | None\n    type: Literal[\"external\"] = \"external\"\n\n\nTypeXRef = TypeXRefExternal | TypeXRefInternal | TypeXRefIntrinsic\n\n\n@define\nclass DescriptionName:\n    text: str\n    type: Literal[\"name\"] = \"name\"\n\n\n@define\nclass DescriptionText:\n    text: str\n    type: Literal[\"text\"] = \"text\"\n\n\n@define\nclass DescriptionCode:\n    code: str\n    type: Literal[\"code\"] = \"code\"\n\n\nDescriptionItem = DescriptionName | DescriptionText | DescriptionCode\n\nDescription = str | Sequence[DescriptionItem]\n\n#: Human-readable type of a value. None if we don't know the type.\nType = str | list[str | TypeXRef] | None\n\n\nclass Pathname:\n    \"\"\"A partial or full path to a language entity.\n\n    Example: ``['./', 'dir/', 'dir/', 'file.', 'object.', 'object#', 'object']``\n\n    \"\"\"\n\n    def __init__(self, segments: Sequence[str]):\n        self.segments = segments\n\n    def __str__(self) -> str:\n        return \"\".join(self.segments)\n\n    def __repr__(self) -> str:\n        return \"Pathname(%r)\" % self.segments\n\n    def __eq__(self, other: Any) -> bool:\n        return isinstance(other, self.__class__) and self.segments == other.segments\n\n    def dotted(self) -> str:\n        return dotted_path(self.segments)\n\n\n@define\nclass _NoDefault:\n    \"\"\"A conspicuous no-default value that will show up in templates to help\n    troubleshoot code paths that grab ``Param.default`` without checking\n    ``Param.has_default`` first.\"\"\"\n\n    _no_default: bool = True\n\n    def __repr__(self) -> str:\n        return \"NO_DEFAULT\"\n\n\nNO_DEFAULT = _NoDefault()\n\n\n@define(slots=False)\nclass _Member:\n    \"\"\"An IR object that is a member of another, as a method is a member of a\n    class or interface\"\"\"\n\n    #: Whether this member is required to be provided by a subclass of a class\n    #: or implementor of an interface\n    is_abstract: bool\n    #: Whether this member is optional in the TypeScript sense of being allowed\n    #: on but not required of an object to conform to a type\n    is_optional: bool\n    #: Whether this member can be accessed on the container itself rather than\n    #: just on instances of it\n    is_static: bool\n    #: Is a private member of a class or, at least in JSDoc, a @namespace:\n    is_private: bool\n\n\n@define\nclass TypeParam:\n    name: str\n    extends: Type\n    description: Description = \"\"\n\n\n@define\nclass Param:\n    \"\"\"A parameter of either a function or (in the case of TS, which has\n    classes parametrized by type) a class.\"\"\"\n\n    name: str\n    #: The description text (like all other description fields in the IR)\n    #: retains any line breaks and subsequent indentation whitespace that were\n    #: in the source code.\n    description: Description = \"\"\n    has_default: bool = False\n    is_variadic: bool = False\n    type: Type | None = None\n    #: Return the default value of this parameter, string-formatted so it can\n    #: be immediately suffixed to an equal sign in a formal param list. For\n    #: example, the number 6 becomes the string \"6\" to create ``foo=6``. If\n    # : has_default=True, this must be set.\n    default: str | _NoDefault = NO_DEFAULT\n\n    def __attrs_post_init__(self) -> None:\n        if self.has_default and self.default is NO_DEFAULT:\n            raise ValueError(\n                \"Tried to construct a Param with has_default=True but without `default` specified.\"\n            )\n\n\n@define\nclass Exc:\n    \"\"\"One kind of exception that can be raised by a function\"\"\"\n\n    #: The type of exception can have\n    type: Type\n    description: Description\n\n\n@define\nclass Return:\n    \"\"\"One kind of thing a function can return\"\"\"\n\n    #: The type this kind of return value can have\n    type: Type\n    description: Description\n\n\n@define\nclass Module:\n    filename: str\n    deppath: str | None\n    path: Pathname\n    line: int\n    attributes: list[\"TopLevel\"] = Factory(list)\n    functions: list[\"Function\"] = Factory(list)\n    classes: list[\"Class\"] = Factory(list)\n    interfaces: list[\"Interface\"] = Factory(list)\n    type_aliases: list[\"TypeAlias\"] = Factory(list)\n\n\n@define(slots=False)\nclass TopLevel:\n    \"\"\"A language object with an independent existence\n\n    A TopLevel entity is a potentially strong entity in the database sense; one\n    of these can exist on its own and not merely as a datum attached to another\n    entity. For example, Returns do not qualify, since they cannot exist\n    without a parent Function. And, though a given Attribute may be attached to\n    a Class, Attributes can also exist top-level in a module.\n\n    These are also complex entities: the sorts of thing with the potential to\n    include the kinds of subentities referenced by the fields defined herein.\n\n    \"\"\"\n\n    #: The short name of the object, regardless of whether it's a class or\n    #: function or typedef or param.\n    #:\n    #: This is usually the same as the last item of path.segments but not\n    #: always. For example, in JSDoc Attributes defined with @property, name is\n    #: defined but path is empty. This was a shortcut and could be corrected at\n    #: some point. If it is, we can stop storing name as a separate field. Also\n    #: TypeScript constructors are named \"new WhateverClass\". They should\n    #: instead be called \"constructor\".\n    name: str\n    #: The namepath-like unambiguous identifier of the object, e.g. ``['./',\n    #: 'dir/', 'dir/', 'file/', 'object.', 'object#', 'object']``\n    path: Pathname\n    #: The basename of the file the object is from, e.g. \"foo.js\"\n    filename: str\n    #: The path to the dependency, i.e., the file the object is from.\n    #: Either absolute or relative to the root_for_relative_js_paths.\n    deppath: str | None\n    #: The human-readable description of the entity or '' if absent\n    description: Description\n    modifier_tags: list[str] = field(kw_only=True, factory=list)\n    block_tags: dict[str, Sequence[Description]] = field(kw_only=True, factory=dict)\n    #: Line number where the object (excluding any prefixing comment) begins\n    line: int | None\n    #: Explanation of the deprecation (which implies True) or True or False\n    deprecated: Description | bool\n    #: List of preformatted textual examples\n    examples: Sequence[Description]\n    #: List of paths to also refer the reader to\n    see_alsos: list[str]\n    #: Explicitly documented sub-properties of the object, a la jsdoc's\n    #: @properties\n    properties: list[\"Attribute\"]\n    #: None if not exported for use by outside code. Otherwise, the Sphinx\n    #: dotted path to the module it is exported from, e.g. 'foo.bar'\n    exported_from: Pathname | None\n    #: Descriminator\n    kind: str = field(kw_only=True)\n    #: Is it a root documentation item? Used by autosummary.\n    documentation_root: bool = field(kw_only=True, default=False)\n\n\n@define(slots=False)\nclass Attribute(TopLevel, _Member):\n    \"\"\"A property of an object\n\n    These are called attributes to match up with Sphinx's autoattribute\n    directive which is used to display them.\n\n    \"\"\"\n\n    #: The type this property's value can have\n    type: Type\n    readonly: bool = False\n    kind: Literal[\"attribute\"] = \"attribute\"\n\n\n@define\nclass Function(TopLevel, _Member):\n    \"\"\"A function or a method of a class\"\"\"\n\n    is_async: bool\n    params: list[Param]\n    exceptions: list[Exc]\n    returns: list[Return]\n    type_params: list[TypeParam] = Factory(list)\n    kind: Literal[\"function\"] = \"function\"\n\n\n@define\nclass _MembersAndSupers:\n    \"\"\"An IR object that can contain members and extend other types\"\"\"\n\n    #: Class members, concretized ahead of time for simplicity. (Otherwise,\n    #: we'd have to pass the doclets_by_class map in and keep it around, along\n    #: with a callable that would create the member IRs from it on demand.)\n    #: Does not include the default constructor.\n    members: list[Function | Attribute]\n    #: Objects this one extends: for example, superclasses of a class or\n    #: superinterfaces of an interface\n    supers: list[Type]\n\n\n@define\nclass Interface(TopLevel, _MembersAndSupers):\n    \"\"\"An interface, a la TypeScript\"\"\"\n\n    type_params: list[TypeParam] = Factory(list)\n    kind: Literal[\"interface\"] = \"interface\"\n\n\n@define\nclass Class(TopLevel, _MembersAndSupers):\n    #: The default constructor for this class. Absent if the constructor is\n    #: inherited.\n    constructor_: Function | None\n    #: Whether this is an abstract class\n    is_abstract: bool\n    #: Interfaces this class implements\n    interfaces: list[Type]\n    # There's room here for additional fields like @example on the class doclet\n    # itself. These are supported and extracted by jsdoc, but they end up in an\n    # `undocumented: True` doclet and so are presently filtered out. But we do\n    # have the space to include them someday.\n    type_params: list[TypeParam] = Factory(list)\n    kind: Literal[\"class\"] = \"class\"\n\n\n@define\nclass TypeAlias(TopLevel):\n    type: Type\n    type_params: list[TypeParam] = Factory(list)\n    kind: Literal[\"typeAlias\"] = \"typeAlias\"\n\n\nTopLevelUnion = Class | Interface | Function | Attribute | TypeAlias\n\n# Now make a serializer/deserializer.\n# TODO: Add tests to make sure that serialization and deserialization are a\n# round trip.\n\n\ndef json_to_ir(json: Any) -> list[TopLevelUnion]:\n    \"\"\"Structure raw json into a list of TopLevels\"\"\"\n    return converter.structure(json, list[TopLevelUnion])\n\n\nconverter = cattrs.Converter()\n# We just serialize Pathname as a list\nconverter.register_unstructure_hook(Pathname, lambda x: x.segments)\nconverter.register_structure_hook(Pathname, lambda x, _: Pathname(x))\n\n# Nothing else needs custom serialization. Add a decorator to register custom\n# deserializers for the various unions.\n\nP = ParamSpec(\"P\")\nT = TypeVar(\"T\")\n\n\ndef _structure(*types: Any) -> Callable[[Callable[P, T]], Callable[P, T]]:\n    def dec(func: Callable[P, T]) -> Callable[P, T]:\n        for ty in types:\n            converter.register_structure_hook(ty, func)\n        return func\n\n    return dec\n\n\n@_structure(Description, Description | bool)\ndef structure_description(x: Any, _: Any) -> Description | bool:\n    if isinstance(x, str):\n        return x\n    if isinstance(x, bool):\n        return x\n    return converter.structure(x, list[DescriptionItem])\n\n\ndef get_type_literal(t: type[DescriptionText]) -> str:\n    \"\"\"Take the \"blah\" from the type annotation in\n\n    type: Literal[\"blah\"]\n    \"\"\"\n    return t.__annotations__[\"type\"].__args__[0]  # type:ignore[no-any-return]\n\n\ndescription_type_map = {\n    get_type_literal(t): t for t in [DescriptionName, DescriptionText, DescriptionCode]\n}\n\n\n@_structure(DescriptionItem)\ndef structure_description_item(x: Any, _: Any) -> DescriptionItem:\n    # Look up the expected type of x from the value of x[\"type\"]\n    return converter.structure(x, description_type_map[x[\"type\"]])\n\n\n@_structure(Type)\ndef structure_type(x: Any, _: Any) -> Type:\n    if isinstance(x, str) or x is None:\n        return x\n    return converter.structure(x, list[str | TypeXRef])\n\n\n@_structure(str | TypeXRef)\ndef structure_str_or_xref(x: Any, _: Any) -> Type:\n    if isinstance(x, str):\n        return x\n    return converter.structure(x, TypeXRef)  # type:ignore[arg-type]\n\n\n@_structure(str | _NoDefault)\ndef structure_str_or_nodefault(x: Any, _: Any) -> str | _NoDefault:\n    if isinstance(x, str):\n        return x\n    return NO_DEFAULT\n"
  },
  {
    "path": "sphinx_js/js/cli.ts",
    "content": "import {\n  Application,\n  ArgumentsReader,\n  TypeDocReader,\n  PackageJsonReader,\n  TSConfigReader,\n  ProjectReflection,\n} from \"typedoc\";\nimport { Converter } from \"./convertTopLevel.ts\";\nimport { SphinxJsConfig } from \"./sphinxJsConfig.ts\";\nimport { fileURLToPath } from \"url\";\nimport { redirectPrivateTypes } from \"./redirectPrivateAliases.ts\";\nimport { TopLevelIR } from \"./ir.ts\";\n\nconst ExitCodes = {\n  Ok: 0,\n  OptionError: 1,\n  CompileError: 3,\n  ValidationError: 4,\n  OutputError: 5,\n  ExceptionThrown: 6,\n  Watching: 7,\n};\n\nexport class ExitError extends Error {\n  code: number;\n  constructor(code: number) {\n    super();\n    this.code = code;\n  }\n}\n\nasync function bootstrapAppTypedoc0_25(args: string[]): Promise<Application> {\n  return await Application.bootstrapWithPlugins(\n    {\n      plugin: [fileURLToPath(import.meta.resolve(\"./typedocPlugin.ts\"))],\n    },\n    [\n      new ArgumentsReader(0, args),\n      new TypeDocReader(),\n      new PackageJsonReader(),\n      new TSConfigReader(),\n      new ArgumentsReader(300, args),\n    ],\n  );\n}\n\nasync function makeApp(args: string[]): Promise<Application> {\n  // Most of this stuff is copied from typedoc/src/lib/cli.ts\n  let app = await bootstrapAppTypedoc0_25(args);\n  if (app.options.getValue(\"version\")) {\n    console.log(app.toString());\n    throw new ExitError(ExitCodes.Ok);\n  }\n  app.extraData = {};\n  app.options.getValue(\"modifierTags\").push(\"@hidetype\", \"@omitFromAutoModule\");\n  app.options.getValue(\"blockTags\").push(\"@destructure\", \"@summaryLink\");\n  return app;\n}\n\nasync function loadConfig(\n  configPath: string | undefined,\n): Promise<SphinxJsConfig> {\n  if (!configPath) {\n    return {};\n  }\n  const configModule = await import(configPath);\n  return configModule.config;\n}\n\nasync function typedocConvert(app: Application): Promise<ProjectReflection> {\n  // Most of this stuff is copied from typedoc/src/lib/cli.ts\n  const project = await app.convert();\n  if (!project) {\n    throw new ExitError(ExitCodes.CompileError);\n  }\n  const preValidationWarnCount = app.logger.warningCount;\n  app.validate(project);\n  const hadValidationWarnings =\n    app.logger.warningCount !== preValidationWarnCount;\n  if (app.logger.hasErrors()) {\n    throw new ExitError(ExitCodes.ValidationError);\n  }\n  if (\n    hadValidationWarnings &&\n    (app.options.getValue(\"treatWarningsAsErrors\") ||\n      app.options.getValue(\"treatValidationWarningsAsErrors\"))\n  ) {\n    throw new ExitError(ExitCodes.ValidationError);\n  }\n  return project;\n}\n\nexport async function run(\n  args: string[],\n): Promise<[Application, TopLevelIR[]]> {\n  let app = await makeApp(args);\n  const userConfigPath = app.options.getValue(\"sphinxJsConfig\");\n  const config = await loadConfig(userConfigPath);\n  app.logger.info(`Loaded user config from ${userConfigPath}`);\n  const symbolToType = redirectPrivateTypes(app);\n  await config.preConvert?.(app);\n  const project = await typedocConvert(app);\n  const basePath = app.options.getValue(\"basePath\");\n  const converter = new Converter(project, basePath, config, symbolToType);\n  converter.computePaths();\n  const result = converter.convertAll();\n  await config.postConvert?.(app, project, converter.typedocToIRMap);\n  return [app, result];\n}\n"
  },
  {
    "path": "sphinx_js/js/convertTopLevel.ts",
    "content": "import {\n  Comment,\n  CommentDisplayPart,\n  DeclarationReflection,\n  ParameterReflection,\n  ProjectReflection,\n  ReferenceType,\n  ReflectionKind,\n  ReflectionVisitor,\n  SignatureReflection,\n  SomeType,\n  TypeContext,\n  TypeParameterReflection,\n} from \"typedoc\";\nimport {\n  referenceToXRef,\n  convertType,\n  convertTypeLiteral,\n} from \"./convertType.ts\";\nimport {\n  NO_DEFAULT,\n  Attribute,\n  Class,\n  Description,\n  DescriptionItem,\n  Interface,\n  IRFunction,\n  Member,\n  Param,\n  Pathname,\n  Return,\n  TopLevelIR,\n  TopLevel,\n  Type,\n  TypeParam,\n} from \"./ir.ts\";\nimport { sep, relative } from \"path\";\nimport { SphinxJsConfig } from \"./sphinxJsConfig.ts\";\nimport { ReadonlySymbolToType } from \"./redirectPrivateAliases.ts\";\n\nexport function parseFilePath(path: string, base_dir: string): string[] {\n  // First we want to know if path is under base_dir.\n  // Get directions from base_dir to the path\n  const rel = relative(base_dir, path);\n  let pathSegments: string[];\n  if (!rel.startsWith(\"..\")) {\n    // We don't have to go up so path is under base_dir\n    pathSegments = rel.split(sep);\n  } else {\n    // It's not under base_dir... maybe it's in a global node_modules or\n    // something? This makes it look the same as if it were under a local\n    // node_modules.\n    pathSegments = path.split(sep);\n    pathSegments.reverse();\n    const idx = pathSegments.indexOf(\"node_modules\");\n    if (idx !== -1) {\n      pathSegments = pathSegments.slice(0, idx + 1);\n    }\n    pathSegments.reverse();\n  }\n  // Remove the file suffix from the last entry if it exists. If there is no .,\n  // then this will leave it alone.\n  let lastEntry = pathSegments.pop();\n  if (lastEntry !== undefined) {\n    pathSegments.push(lastEntry.slice(0, lastEntry.lastIndexOf(\".\")));\n  }\n  // Add a . to the start and a / after every entry so that if we join the\n  // entries it looks like the correct relative path.\n  // Hopefully it was actually a relative path of some sort...\n  pathSegments.unshift(\".\");\n  for (let i = 0; i < pathSegments.length - 1; i++) {\n    pathSegments[i] += \"/\";\n  }\n  return pathSegments;\n}\n\n/**\n * We currently replace {a : () => void} with {a() => void}. \"a\" is a reflection\n * with a TypeLiteral type kind, and the type has name \"__type\". We don't want\n * this to appear in the docs so we have to check for it and remove it.\n */\nfunction isAnonymousTypeLiteral(\n  refl: DeclarationReflection | SignatureReflection,\n): boolean {\n  return refl.kindOf(ReflectionKind.TypeLiteral) && refl.name === \"__type\";\n}\n\n/**\n * A ReflectionVisitor that computes the path for each reflection for us.\n *\n * We want to compute the paths for both DeclarationReflections and\n * SignatureReflections.\n */\nclass PathComputer implements ReflectionVisitor {\n  readonly basePath: string;\n  // The maps we're trying to fill in.\n  readonly pathMap: Map<DeclarationReflection | SignatureReflection, Pathname>;\n  readonly filePathMap: Map<\n    DeclarationReflection | SignatureReflection,\n    Pathname\n  >;\n  // Record which reflections are documentation roots. Used in sphinx for\n  // automodule and autosummary directives.\n  readonly documentationRoots: Set<DeclarationReflection | SignatureReflection>;\n\n  // State for the visitor\n  parentKind: ReflectionKind | undefined;\n  parentSegments: string[];\n  filePath: string[];\n  constructor(\n    basePath: string,\n    pathMap: Map<DeclarationReflection | SignatureReflection, Pathname>,\n    filePathMap: Map<DeclarationReflection | SignatureReflection, Pathname>,\n    documentationRoots: Set<DeclarationReflection | SignatureReflection>,\n  ) {\n    this.pathMap = pathMap;\n    this.filePathMap = filePathMap;\n    this.basePath = basePath;\n    this.documentationRoots = documentationRoots;\n    this.parentKind = undefined;\n    this.parentSegments = [];\n    this.filePath = [];\n  }\n\n  /**\n   * If the name of the reflection is supposed to be a symbol, it should look\n   * something like [Symbol.iterator] but typedoc just shows it as [iterator].\n   * Downstream lexers to color the docs split on dots, but we don't want that\n   * because here the dot is part of the name. Instead, we add a dot lookalike.\n   */\n  static fixSymbolName(refl: DeclarationReflection | SignatureReflection) {\n    const SYMBOL_PREFIX = \"[Symbol\\u2024\";\n    if (refl.name.startsWith(\"[\") && !refl.name.startsWith(SYMBOL_PREFIX)) {\n      // Probably a symbol (are there other reasons the name would start with \"[\"?)\n      // \\u2024 looks like a period but is not a period.\n      // This isn't ideal, but otherwise the coloring is weird.\n      refl.name = SYMBOL_PREFIX + refl.name.slice(1);\n    }\n  }\n\n  /**\n   * The main logic for this visitor. static for easier readability.\n   */\n  static computePath(\n    refl: DeclarationReflection | SignatureReflection,\n    parentKind: ReflectionKind,\n    parentSegments: string[],\n    filePath: string[],\n  ): Pathname {\n    // If no parentSegments, this is a \"root\", use the file path as the\n    // parentSegments.\n    // We have to copy the segments because we're going to mutate it.\n    const segments = Array.from(\n      parentSegments.length > 0 ? parentSegments : filePath,\n    );\n    // Skip some redundant names\n    const suppressReflName =\n      refl.kindOf(\n        // Module names are redundant with the file path\n        ReflectionKind.Module |\n          // Signature names are redundant with the callable. TODO: do we want to\n          // handle callables with multiple signatures?\n          ReflectionKind.ConstructorSignature |\n          ReflectionKind.CallSignature,\n      ) || isAnonymousTypeLiteral(refl);\n    if (suppressReflName) {\n      return segments;\n    }\n    if (segments.length > 0) {\n      // Add delimiter. For most things use a . e.g., parent.name but for\n      // nonstatic class members we write Class#member\n      const delimiter =\n        parentKind === ReflectionKind.Class && !refl.flags.isStatic ? \"#\" : \".\";\n      segments[segments.length - 1] += delimiter;\n    }\n    // Add the name of the current reflection to the list\n    segments.push(refl.name);\n    return segments;\n  }\n\n  setPath(refl: DeclarationReflection | SignatureReflection): Pathname {\n    PathComputer.fixSymbolName(refl);\n    const segments = PathComputer.computePath(\n      refl,\n      this.parentKind!,\n      this.parentSegments,\n      this.filePath,\n    );\n    if (isAnonymousTypeLiteral(refl)) {\n      // Rename the anonymous type literal to share its name with the attribute\n      // it is the type of.\n      refl.name = this.parentSegments.at(-1)!;\n    }\n    this.pathMap.set(refl, segments);\n    this.filePathMap.set(refl, this.filePath);\n    return segments;\n  }\n\n  // The visitor methods\n\n  project(project: ProjectReflection) {\n    // Compute the set of documentation roots.\n    // This consists of all children of the Project and all children of Modules.\n    for (const child of project.children || []) {\n      this.documentationRoots.add(child);\n    }\n    for (const module of project.getChildrenByKind(ReflectionKind.Module)) {\n      for (const child of module.children || []) {\n        this.documentationRoots.add(child);\n      }\n    }\n    // Visit children\n    project.children?.forEach((x) => x.visit(this));\n  }\n\n  declaration(refl: DeclarationReflection) {\n    if (refl.sources) {\n      this.filePath = parseFilePath(refl.sources![0].fileName, this.basePath);\n    }\n    const segments = this.setPath(refl);\n    // Update state for children\n    const origParentSegs = this.parentSegments;\n    const origParentKind = this.parentKind;\n    this.parentSegments = segments;\n    this.parentKind = refl.kind;\n    // Visit children\n    refl.children?.forEach((child) => child.visit(this));\n    refl.signatures?.forEach((child) => child.visit(this));\n    if (\n      refl.kind === ReflectionKind.Property &&\n      refl.type?.type == \"reflection\"\n    ) {\n      // If the property has a function type, we replace it with a function\n      // described by the declaration. Just in case that happens we compute the\n      // path for the declaration here.\n      refl.type.declaration.visit(this);\n    }\n    // Restore state\n    this.parentSegments = origParentSegs;\n    this.parentKind = origParentKind;\n  }\n\n  signature(refl: SignatureReflection) {\n    this.setPath(refl);\n  }\n}\n\n// Some utilities for manipulating comments\n\n/**\n * Convert CommentDisplayParts from typedoc IR to sphinx-js comment IR.\n * @param content List of CommentDisplayPart\n * @returns\n */\nfunction renderCommentContent(content: CommentDisplayPart[]): Description {\n  return content.map((x): DescriptionItem => {\n    if (x.kind === \"code\") {\n      return { type: \"code\", code: x.text };\n    }\n    if (x.kind === \"text\") {\n      return { type: \"text\", text: x.text };\n    }\n    throw new Error(\"Not implemented\");\n  });\n}\n\nfunction renderCommentSummary(c: Comment | undefined): Description {\n  if (!c) {\n    return [];\n  }\n  return renderCommentContent(c.summary);\n}\n\n/**\n * Compute a map from blockTagName to list of comment descriptions.\n */\nfunction getCommentBlockTags(c: Comment | undefined): {\n  [key: string]: Description[];\n} {\n  if (!c) {\n    return {};\n  }\n  const result: { [key: string]: Description[] } = {};\n  for (const tag of c.blockTags) {\n    const tagType = tag.tag.slice(1);\n    if (!(tagType in result)) {\n      result[tagType] = [];\n    }\n    const content: Description = [];\n    if (tag.name) {\n      // If the tag has a name field, add it as a DescriptionName\n      content.push({\n        type: \"name\",\n        text: tag.name,\n      });\n    }\n    content.push(...renderCommentContent(tag.content));\n    result[tagType].push(content);\n  }\n  return result;\n}\n\n/**\n * The type returned by most methods on the converter.\n *\n * A pair, an optional TopLevel and an optional list of additional reflections\n * to convert.\n */\ntype ConvertResult = [\n  TopLevelIR | undefined,\n  DeclarationReflection[] | undefined,\n];\n\n/**\n * We generate some \"synthetic parameters\" when destructuring parameters. It\n * would be possible to convert directly to our IR but it causes some code\n * duplication. Instead, we keep track of the subset of fields that `paramToIR`\n * actually needs here.\n */\ntype ParamReflSubset = Pick<\n  ParameterReflection,\n  \"comment\" | \"defaultValue\" | \"flags\" | \"name\" | \"type\"\n>;\n\n/**\n * Main class for creating IR from the ProjectReflection.\n *\n * The main toIr logic is a sort of visitor for ReflectionKinds. We don't use\n * ReflectionVisitor because the division it uses for visitor methods is too\n * coarse.\n *\n * We visit in a breadth-first order, not for any super compelling reason.\n */\nexport class Converter {\n  readonly project: ProjectReflection;\n  readonly basePath: string;\n  readonly config: SphinxJsConfig;\n  readonly symbolToType: ReadonlySymbolToType;\n\n  readonly pathMap: Map<DeclarationReflection | SignatureReflection, Pathname>;\n  readonly filePathMap: Map<\n    DeclarationReflection | SignatureReflection,\n    Pathname\n  >;\n  readonly documentationRoots: Set<DeclarationReflection | SignatureReflection>;\n  readonly typedocToIRMap: Map<DeclarationReflection, TopLevel>;\n\n  constructor(\n    project: ProjectReflection,\n    basePath: string,\n    config: SphinxJsConfig,\n    symbolToType: ReadonlySymbolToType,\n  ) {\n    this.project = project;\n    this.basePath = basePath;\n    this.config = config;\n    this.symbolToType = symbolToType;\n\n    this.pathMap = new Map();\n    this.filePathMap = new Map();\n    this.documentationRoots = new Set();\n    this.typedocToIRMap = new Map();\n  }\n\n  convertType(type: SomeType, context: TypeContext = TypeContext.none): Type {\n    return convertType(\n      this.basePath,\n      this.pathMap,\n      this.symbolToType,\n      type,\n      context,\n    );\n  }\n\n  referenceToXRef(type: ReferenceType): Type {\n    return referenceToXRef(\n      this.basePath,\n      this.pathMap,\n      this.symbolToType,\n      type,\n    );\n  }\n\n  computePaths() {\n    this.project.visit(\n      new PathComputer(\n        this.basePath,\n        this.pathMap,\n        this.filePathMap,\n        this.documentationRoots,\n      ),\n    );\n  }\n\n  /**\n   * Convert all Reflections.\n   */\n  convertAll(): TopLevelIR[] {\n    const todo = Array.from(this.project.children!);\n    const result: TopLevelIR[] = [];\n    while (todo.length) {\n      const node = todo.pop()!;\n      const [converted, rest] = this.toIr(node);\n      if (converted) {\n        this.typedocToIRMap.set(node, converted);\n        result.push(converted);\n      }\n      todo.push(...(rest || []));\n    }\n    return result;\n  }\n\n  /**\n   * Convert the reflection and return a pair, the conversion result and a list\n   * of descendent Reflections to convert. These descendents are either children\n   * or signatures.\n   *\n   * @param object The reflection to convert\n   * @returns A pair, a possible result IR object, and a list of descendent\n   * Reflections that still need converting.\n   */\n  toIr(object: DeclarationReflection | SignatureReflection): ConvertResult {\n    // ReflectionKinds that we give no conversion.\n    if (\n      object.kindOf(\n        ReflectionKind.Module |\n          ReflectionKind.Namespace |\n          // TODO: document enums\n          ReflectionKind.Enum |\n          ReflectionKind.EnumMember |\n          // A ReferenceReflection is when we reexport something.\n          // TODO: should we handle this somehow?\n          ReflectionKind.Reference,\n      )\n    ) {\n      // TODO: The children of these have no rendered parent in the docs. If\n      // \"object\" is marked as a documentation_root, maybe the children should\n      // be too?\n      return [undefined, (object as DeclarationReflection).children];\n    }\n    const kind = ReflectionKind[object.kind];\n    const convertFunc = `convert${kind}` as keyof this;\n    if (!this[convertFunc]) {\n      throw new Error(`No known converter for kind ${kind}`);\n    }\n    // @ts-ignore\n    const result: ConvertResult = this[convertFunc](object);\n    if (this.documentationRoots.has(object) && result[0]) {\n      result[0].documentation_root = true;\n    }\n    return result;\n  }\n\n  // Reflection visitor methods\n\n  convertFunction(func: DeclarationReflection): ConvertResult {\n    return [this.functionToIR(func), func.children];\n  }\n  convertMethod(func: DeclarationReflection): ConvertResult {\n    return [this.functionToIR(func), func.children];\n  }\n  convertConstructor(func: DeclarationReflection): ConvertResult {\n    return [this.functionToIR(func), func.children];\n  }\n  convertVariable(v: DeclarationReflection): ConvertResult {\n    if (!v.type) {\n      throw new Error(`Type of ${v.name} is undefined`);\n    }\n    let type: Type;\n    if (v.comment?.modifierTags.has(\"@hidetype\")) {\n      type = [];\n    } else {\n      type = this.convertType(v.type);\n    }\n    const result: Attribute = {\n      ...this.memberProps(v),\n      ...this.topLevelProperties(v),\n      readonly: false,\n      kind: \"attribute\",\n      type,\n    };\n    return [result, v.children];\n  }\n\n  /**\n   * Return the unambiguous pathnames of implemented interfaces or extended\n   * classes.\n   */\n  relatedTypes(\n    cls: DeclarationReflection,\n    kind: \"extendedTypes\" | \"implementedTypes\",\n  ): Type[] {\n    const origTypes = cls[kind] || [];\n    const result: Type[] = [];\n    for (const t of origTypes) {\n      if (t.type !== \"reference\") {\n        continue;\n      }\n      result.push(this.referenceToXRef(t));\n    }\n    return result;\n  }\n\n  convertClass(cls: DeclarationReflection): ConvertResult {\n    const [constructor_, members] = this.constructorAndMembers(cls);\n    const result: Class = {\n      constructor_,\n      members,\n      supers: this.relatedTypes(cls, \"extendedTypes\"),\n      is_abstract: cls.flags.isAbstract,\n      interfaces: this.relatedTypes(cls, \"implementedTypes\"),\n      type_params: this.typeParamsToIR(cls.typeParameters),\n      ...this.topLevelProperties(cls),\n      kind: \"class\",\n    };\n    return [result, cls.children];\n  }\n\n  convertInterface(cls: DeclarationReflection): ConvertResult {\n    const [_, members] = this.constructorAndMembers(cls);\n    const result: Interface = {\n      members,\n      supers: this.relatedTypes(cls, \"extendedTypes\"),\n      type_params: this.typeParamsToIR(cls.typeParameters),\n      ...this.topLevelProperties(cls),\n      kind: \"interface\",\n    };\n    return [result, cls.children];\n  }\n\n  convertProperty(prop: DeclarationReflection): ConvertResult {\n    if (\n      prop.type?.type === \"reflection\" &&\n      prop.type.declaration.kindOf(ReflectionKind.TypeLiteral) &&\n      prop.type.declaration.signatures?.length\n    ) {\n      // Render {f: () => void} like {f(): void}\n      // TODO: unclear if this is the right behavior. Maybe there should be a\n      // way to pick?\n      const functionIR = this.functionToIR(prop.type.declaration);\n\n      // Preserve the property's own documentation if it exists\n      functionIR.description = renderCommentSummary(prop.comment);\n\n      // Preserve the optional flag from the original property\n      functionIR.is_optional = prop.flags.isOptional;\n\n      return [functionIR, []];\n    }\n    let type: Type;\n    if (prop.comment?.modifierTags.has(\"@hidetype\")) {\n      // We should probably also be able to hide the type of a thing with a\n      // function type literal type...\n      type = [];\n    } else {\n      type = this.convertType(prop.type!);\n    }\n    const result: Attribute = {\n      type,\n      ...this.memberProps(prop),\n      ...this.topLevelProperties(prop),\n      description: renderCommentSummary(prop.comment),\n      readonly: prop.flags.isReadonly,\n      kind: \"attribute\",\n    };\n    return [result, prop.children];\n  }\n\n  /**\n   * An Accessor is a thing with a getter or a setter. It should look exactly\n   * like a Property in the rendered docs since the distinction is an\n   * implementation detail.\n   *\n   * Specifically:\n   * 1. an Accessor with a getter but no setter should be rendered as a readonly\n   *    Property.\n   * 2. an Accessor with a getter and a setter should be rendered as a\n   *    read/write Property\n   * 3. Not really sure what to do with an Accessor with a setter and no getter.\n   *    That's kind of weird.\n   */\n  convertAccessor(prop: DeclarationReflection): ConvertResult {\n    let type: SomeType;\n    let sig: SignatureReflection;\n    if (prop.getSignature) {\n      // There's no signature to speak of for a getter: only a return type.\n      sig = prop.getSignature;\n      type = sig.type!;\n    } else {\n      if (!prop.setSignature) {\n        throw new Error(\"???\");\n      }\n      // ES6 says setters have exactly 1 param.\n      sig = prop.setSignature;\n      type = sig.parameters![0].type!;\n    }\n    // If there's no setter say it's readonly\n    const readonly = !prop.setSignature;\n    const result: Attribute = {\n      type: this.convertType(type),\n      readonly,\n      ...this.memberProps(prop),\n      ...this.topLevelProperties(prop),\n      kind: \"attribute\",\n    };\n    result.description = renderCommentSummary(sig.comment);\n    return [result, prop.children];\n  }\n\n  convertClassChild(child: DeclarationReflection): IRFunction | Attribute {\n    if (\n      !child.kindOf(\n        ReflectionKind.Accessor |\n          ReflectionKind.Constructor |\n          ReflectionKind.Method |\n          ReflectionKind.Property,\n      )\n    ) {\n      throw new TypeError(\n        \"Expected an Accessor, Constructor, Method, or Property\",\n      );\n    }\n    // Should we assert that the \"descendants\" component is empty?\n    return this.toIr(child)[0] as IRFunction | Attribute;\n  }\n\n  /**\n   * Return the IR for the constructor and other members of a class or\n   * interface.\n   *\n   * In TS, a constructor may have multiple (overloaded) type signatures but\n   * only one implementation. (Same with functions.) So there's at most 1\n   * constructor to return. Return None for the constructor if it is inherited\n   * or implied rather than explicitly present in the class.\n   *\n   * @param refl Class or Interface\n   * @returns A tuple of (constructor Function, list of other members)\n   */\n  constructorAndMembers(\n    refl: DeclarationReflection,\n  ): [IRFunction | null, (IRFunction | Attribute)[]] {\n    let constructor: IRFunction | null = null;\n    const members: (IRFunction | Attribute)[] = [];\n    for (const child of refl.children || []) {\n      if (child.inheritedFrom) {\n        continue;\n      }\n      if (child.kindOf(ReflectionKind.Constructor)) {\n        // This really, really should happen exactly once per class.\n        constructor = this.functionToIR(child);\n        constructor.returns = [];\n        continue;\n      }\n      members.push(this.convertClassChild(child));\n    }\n    return [constructor, members];\n  }\n\n  /**\n   * Compute common properties for all class members.\n   */\n  memberProps(refl: DeclarationReflection): Member {\n    return {\n      is_abstract: refl.flags.isAbstract,\n      is_optional: refl.flags.isOptional,\n      is_static: refl.flags.isStatic,\n      is_private: refl.flags.isPrivate,\n    };\n  }\n\n  /**\n   * Compute common properties for all TopLevels.\n   */\n  topLevelProperties(\n    refl: DeclarationReflection | SignatureReflection,\n  ): TopLevel {\n    const path = this.pathMap.get(refl);\n    const filePath = this.filePathMap.get(refl)!;\n    if (!path) {\n      throw new Error(`Missing path for ${refl.name}`);\n    }\n    const block_tags = getCommentBlockTags(refl.comment);\n    let deprecated: Description | boolean =\n      block_tags[\"deprecated\"]?.[0] || false;\n    if (deprecated && deprecated.length === 0) {\n      deprecated = true;\n    }\n    return {\n      name: refl.name,\n      path,\n      deppath: filePath.join(\"\"),\n      filename: \"\",\n      description: renderCommentSummary(refl.comment),\n      modifier_tags: Array.from(refl.comment?.modifierTags || []),\n      block_tags,\n      deprecated,\n      examples: block_tags[\"example\"] || [],\n      properties: [],\n      see_alsos: [],\n      exported_from: filePath,\n      line: refl.sources?.[0].line || null,\n      documentation_root: false,\n    };\n  }\n\n  /**\n   * We want to document a destructured argument as if it were several separate\n   * arguments. This finds complex inline object types in the arguments list of\n   * a function and \"destructures\" them into separately documented arguments.\n   *\n   * E.g., a function\n   *\n   *       /**\n   *       * @param options\n   *       * @destructure options\n   *       *./\n   *       function f({x , y } : {\n   *           /** The x value *./\n   *           x : number,\n   *           /** The y value *./\n   *           y : string\n   *       }){ ... }\n   *\n   * should be documented like:\n   *\n   *       options.x (number) The x value\n   *       options.y (number) The y value\n   */\n  _destructureParam(param: ParameterReflection): ParamReflSubset[] {\n    const type = param.type;\n    if (type?.type !== \"reflection\") {\n      throw new Error(\"Unexpected\");\n    }\n    const decl = type.declaration;\n    const children = decl.children!;\n    // Sort destructured parameter by order in the type declaration in the\n    // source file. Before we sort they are in alphabetical order by name. Maybe\n    // we should have a way to pick the desired behavior? There are three\n    // reasonable orders:\n    //\n    // 1. alphabetical by name\n    // 2. In order of the @options.b annotations\n    // 3. In order of their declarations in the type\n    //\n    // This does order 3\n    children.sort(\n      ({ sources: a }, { sources: b }) =>\n        a![0].line - b![0].line || a![0].character - b![0].character,\n    );\n    const result: ParamReflSubset[] = [];\n    for (const child of children) {\n      result.push({\n        name: param.name + \".\" + child.name,\n        type: child.type,\n        comment: child.comment,\n        defaultValue: undefined,\n        flags: child.flags,\n      });\n    }\n    return result;\n  }\n\n  _destructureParams(sig: SignatureReflection): ParamReflSubset[] {\n    const result = [];\n    // Destructure a parameter if it's type is a reflection and it is requested\n    // with @destructure or _shouldDestructureArg.\n    const destructureTargets = sig.comment\n      ?.getTags(\"@destructure\")\n      .flatMap((tag) => tag.content[0].text.split(\" \"));\n    const shouldDestructure = (p: ParameterReflection) => {\n      if (p.type?.type !== \"reflection\") {\n        return false;\n      }\n      if (destructureTargets?.includes(p.name)) {\n        return true;\n      }\n      const shouldDestructure = this.config.shouldDestructureArg;\n      return shouldDestructure && shouldDestructure(p);\n    };\n    for (const p of sig.parameters || []) {\n      if (shouldDestructure(p)) {\n        result.push(...this._destructureParam(p));\n      } else {\n        result.push(p);\n      }\n    }\n    return result;\n  }\n\n  /**\n   * Convert a signature parameter\n   */\n  paramToIR(param: ParamReflSubset): Param {\n    let type: Type = [];\n    if (param.type) {\n      type = this.convertType(param.type);\n    }\n    let description = renderCommentSummary(param.comment);\n    if (description.length === 0 && param.type?.type === \"reflection\") {\n      // If the parameter type is given as the typeof something else, use the\n      // description from the target?\n      // TODO: isn't this a weird thing to do here? I think we should remove it?\n      description = renderCommentSummary(\n        param.type.declaration?.signatures?.[0].comment,\n      );\n    }\n    return {\n      name: param.name,\n      has_default: !!param.defaultValue,\n      default: param.defaultValue || NO_DEFAULT,\n      is_variadic: param.flags.isRest,\n      description,\n      type,\n    };\n  }\n  /**\n   * Convert callables: Function, Method, and Constructor.\n   * @param func\n   * @returns\n   */\n  functionToIR(func: DeclarationReflection): IRFunction {\n    // There's really nothing in the function itself; all the interesting bits\n    // are in the 'signatures' property. We support only the first signature at\n    // the moment, because to do otherwise would create multiple identical\n    // pathnames to the same function, which would cause the suffix tree to\n    // raise an exception while being built. An eventual solution might be to\n    // store the signatures in a one-to- many attr of Functions.\n    const first_sig = func.signatures![0]; // Should always have at least one\n\n    // Make sure name matches, can be different in case this comes from\n    // isAnonymousTypeLiteral returning true.\n    first_sig.name = func.name;\n    const params = this._destructureParams(first_sig);\n    let returns: Return[] = [];\n    let is_async = false;\n    // We want to suppress the return type for constructors (it's technically\n    // correct that it returns a class instance but it looks weird).\n    // Also hide explicit void return type.\n    const voidReturnType =\n      func.kindOf(ReflectionKind.Constructor) ||\n      !first_sig.type ||\n      (first_sig.type.type === \"intrinsic\" && first_sig.type.name === \"void\");\n    let type_params = this.typeParamsToIR(first_sig.typeParameters);\n    if (func.kindOf(ReflectionKind.Constructor)) {\n      // I think this is wrong\n      // TODO: remove it\n      type_params = this.typeParamsToIR(\n        (func.parent as DeclarationReflection).typeParameters,\n      );\n    }\n    const topLevel = this.topLevelProperties(first_sig);\n    if (!voidReturnType && first_sig.type) {\n      // Compute return comment and return annotation.\n      const returnType = this.convertType(first_sig.type);\n      const description = topLevel.block_tags.returns?.[0] || [];\n      returns = [{ type: returnType, description }];\n      // Put async in front of the function if it returns a Promise.\n      // Question: Is there any important difference between an actual async\n      // function and a non-async one that returns a Promise?\n      is_async =\n        first_sig.type.type === \"reference\" &&\n        first_sig.type.name === \"Promise\";\n    }\n    return {\n      ...topLevel,\n      ...this.memberProps(func),\n      is_async,\n      params: params?.map(this.paramToIR.bind(this)) || [],\n      type_params,\n      returns,\n      exceptions: [],\n      kind: \"function\",\n    };\n  }\n  typeParamsToIR(\n    typeParams: TypeParameterReflection[] | undefined,\n  ): TypeParam[] {\n    return typeParams?.map((typeParam) => this.typeParamToIR(typeParam)) || [];\n  }\n\n  typeParamToIR(typeParam: TypeParameterReflection): TypeParam {\n    const extends_ = typeParam.type\n      ? this.convertType(typeParam.type, TypeContext.referenceTypeArgument)\n      : null;\n    return {\n      name: typeParam.name,\n      extends: extends_,\n      description: renderCommentSummary(typeParam.comment),\n    };\n  }\n\n  convertTypeAlias(ty: DeclarationReflection): ConvertResult {\n    let type;\n    if (ty.type) {\n      type = this.convertType(ty.type);\n    } else {\n      // Handle this change:\n      // https://github.com/TypeStrong/typedoc/commit/ca94f7eaecf90c25d6377e20c405626817de1e26#diff-14759d25b74ca53aee4558d0e26c85eee3c13484ea3ccdf28872b906829ef6f8R380-R390\n      type = convertTypeLiteral(\n        this.basePath,\n        this.pathMap,\n        this.symbolToType,\n        ty,\n      );\n    }\n    const ir: TopLevelIR = {\n      ...this.topLevelProperties(ty),\n      kind: \"typeAlias\",\n      type,\n      type_params: this.typeParamsToIR(ty.typeParameters),\n    };\n    return [ir, ty.children];\n  }\n}\n"
  },
  {
    "path": "sphinx_js/js/convertType.ts",
    "content": "import {\n  ArrayType,\n  ConditionalType,\n  DeclarationReflection,\n  IndexedAccessType,\n  InferredType,\n  IntersectionType,\n  IntrinsicType,\n  LiteralType,\n  MappedType,\n  NamedTupleMember,\n  OptionalType,\n  PredicateType,\n  QueryType,\n  ReferenceType,\n  ReflectionKind,\n  ReflectionType,\n  RestType,\n  SignatureReflection,\n  SomeType,\n  TemplateLiteralType,\n  TupleType,\n  TypeContext,\n  TypeOperatorType,\n  TypeVisitor,\n  UnionType,\n  UnknownType,\n} from \"typedoc\";\nimport {\n  Type,\n  TypeXRefExternal,\n  TypeXRefInternal,\n  intrinsicType,\n} from \"./ir.js\";\nimport { parseFilePath } from \"./convertTopLevel.js\";\nimport { ReadonlySymbolToType } from \"./redirectPrivateAliases.js\";\n\n/**\n * Convert types into a list of strings and XRefs.\n *\n * Most visitor nodes should be similar to the implementation of getTypeString\n * on the same type.\n */\nclass TypeConverter implements TypeVisitor<Type> {\n  private readonly basePath: string;\n  // For resolving XRefs.\n  private readonly reflToPath: ReadonlyMap<\n    DeclarationReflection | SignatureReflection,\n    string[]\n  >;\n  private readonly symbolToType: ReadonlySymbolToType;\n\n  constructor(\n    basePath: string,\n    reflToPath: ReadonlyMap<\n      DeclarationReflection | SignatureReflection,\n      string[]\n    >,\n    symbolToType: ReadonlySymbolToType,\n  ) {\n    this.basePath = basePath;\n    this.reflToPath = reflToPath;\n    this.symbolToType = symbolToType;\n  }\n\n  /**\n   * Helper for inserting type parameters\n   */\n  addTypeArguments(\n    type: { typeArguments?: SomeType[] | undefined },\n    l: Type,\n  ): Type {\n    if (!type.typeArguments || type.typeArguments.length === 0) {\n      return l;\n    }\n    l.push(\"<\");\n    for (const arg of type.typeArguments) {\n      l.push(...arg.visit(this));\n      l.push(\", \");\n    }\n    l.pop();\n    l.push(\">\");\n    return l;\n  }\n\n  /**\n   * Convert the type, maybe add parentheses\n   */\n  convert(type: SomeType, context: TypeContext): Type {\n    const result = type.visit(this);\n    if (type.needsParenthesis(context)) {\n      result.unshift(\"(\");\n      result.push(\")\");\n    }\n    return result;\n  }\n\n  conditional(type: ConditionalType): Type {\n    return [\n      ...this.convert(type.checkType, TypeContext.conditionalCheck),\n      \" extends \",\n      ...this.convert(type.extendsType, TypeContext.conditionalExtends),\n      \" ? \",\n      ...this.convert(type.trueType, TypeContext.conditionalTrue),\n      \" : \",\n      ...this.convert(type.falseType, TypeContext.conditionalFalse),\n    ];\n  }\n  indexedAccess(type: IndexedAccessType): Type {\n    return [\n      ...this.convert(type.objectType, TypeContext.indexedObject),\n      \"[\",\n      ...this.convert(type.indexType, TypeContext.indexedIndex),\n      \"]\",\n    ];\n  }\n  inferred(type: InferredType): Type {\n    if (type.constraint) {\n      return [\n        `infer ${type.name} extends `,\n        ...this.convert(type.constraint, TypeContext.inferredConstraint),\n      ];\n    }\n    return [`infer ${type.name}`];\n  }\n  intersection(type: IntersectionType): Type {\n    const result: Type = [];\n    for (const elt of type.types) {\n      result.push(...this.convert(elt, TypeContext.intersectionElement));\n      result.push(\" & \");\n    }\n    result.pop();\n    return result;\n  }\n  intrinsic(type: IntrinsicType): Type {\n    return [intrinsicType(type.name)];\n  }\n  literal(type: LiteralType): Type {\n    if (type.value === null) {\n      return [intrinsicType(\"null\")];\n    }\n    return [JSON.stringify(type.value)];\n  }\n  mapped(type: MappedType): Type {\n    const read = {\n      \"+\": \"readonly \",\n      \"-\": \"-readonly \",\n      \"\": \"\",\n    }[type.readonlyModifier ?? \"\"];\n\n    const opt = {\n      \"+\": \"?\",\n      \"-\": \"-?\",\n      \"\": \"\",\n    }[type.optionalModifier ?? \"\"];\n\n    const parts: Type = [\n      \"{ \",\n      read,\n      \"[\",\n      type.parameter,\n      \" in \",\n      ...this.convert(type.parameterType, TypeContext.mappedParameter),\n    ];\n\n    if (type.nameType) {\n      parts.push(\n        \" as \",\n        ...this.convert(type.nameType, TypeContext.mappedName),\n      );\n    }\n\n    parts.push(\n      \"]\",\n      opt,\n      \": \",\n      ...this.convert(type.templateType, TypeContext.mappedTemplate),\n      \" }\",\n    );\n    return parts;\n  }\n  optional(type: OptionalType): Type {\n    return [\n      ...this.convert(type.elementType, TypeContext.optionalElement),\n      \"?\",\n    ];\n  }\n  predicate(type: PredicateType): Type {\n    // Consider using typedoc's representation for this instead of this custom\n    // string.\n    return [\n      intrinsicType(\"boolean\"),\n      \" (typeguard for \",\n      ...type.targetType!.visit(this),\n      \")\",\n    ];\n  }\n  query(type: QueryType): Type {\n    return [\n      \"typeof \",\n      ...this.convert(type.queryType, TypeContext.queryTypeTarget),\n    ];\n  }\n  /**\n   * If it's a reference to a private type alias, replace it with a reflection.\n   * Otherwise return undefined.\n   */\n  convertPrivateReferenceToReflection(type: ReferenceType): Type | undefined {\n    if (type.reflection) {\n      const refl = type.reflection as DeclarationReflection;\n\n      // If it's private, we don't really want to emit an XRef to it. In the\n      // typedocPlugin.ts we tried to calculate Reflections for these, so now\n      // we try to look it up. I couldn't get the line+column numbers to match\n      // up so in this case we index on file name and reference name.\n\n      // Another place where we incorrectly handle merged declarations\n      const src = refl?.sources?.[0];\n      if (!src) {\n        return undefined;\n      }\n      const newTarget = this.symbolToType.get(\n        `${src.fullFileName}:${refl.name}`,\n      );\n      if (newTarget) {\n        // TODO: this doesn't handle parentheses correctly.\n        return newTarget.visit(this);\n      }\n      return undefined;\n    }\n\n    if (!type.symbolId) {\n      throw new Error(\"This should not happen\");\n    }\n    // See if this refers to a private type. In that case we should inline the\n    // type reflection rather than referring to the non-exported name. Ideally\n    // we should key on position rather than name (the same file can have\n    // multiple private types with the same name potentially). But it doesn't\n    // seem to be working.\n    const newTarget = this.symbolToType.get(\n      `${type.symbolId.fileName}:${type.name}`,\n    );\n    if (newTarget) {\n      // TODO: this doesn't handle parentheses correctly.\n      return newTarget.visit(this);\n    }\n    return undefined;\n  }\n  /**\n   * Convert a reference type to either an XRefExternal or an XRefInternal. It\n   * works on things that `convertPrivateReferenceToReflection` but it will\n   * throw an error if the type `isIntentionallyBroken`.\n   *\n   * This logic is also used for relatedTypes for classes (extends and\n   * implements).\n   * TODO: handle type arguments in extends and implements.\n   */\n  convertReferenceToXRef(type: ReferenceType): Type {\n    if (type.isIntentionallyBroken()) {\n      throw new Error(\"Bad type\");\n    }\n\n    if (type.reflection) {\n      const path = this.reflToPath.get(\n        type.reflection as DeclarationReflection,\n      );\n      if (!path) {\n        throw new Error(\n          `Broken internal xref to ${type.reflection?.toStringHierarchy()}`,\n        );\n      }\n      const xref: TypeXRefInternal = {\n        name: type.name,\n        path,\n        type: \"internal\",\n      };\n      return this.addTypeArguments(type, [xref]);\n    }\n\n    if (!type.symbolId) {\n      throw new Error(\"This shouldn't happen\");\n    }\n\n    const path = parseFilePath(type.symbolId?.fileName ?? \"\", this.basePath);\n    if (path.includes(\"node_modules/\")) {\n      // External reference\n      const xref: TypeXRefExternal = {\n        name: type.name,\n        package: type.package!,\n        qualifiedName: type.symbolId.qualifiedName || null,\n        sourcefilename: type.symbolId.fileName || null,\n        type: \"external\",\n      };\n      return this.addTypeArguments(type, [xref]);\n    } else {\n      // TODO: I'm not sure that it's right to generate an internal xref here.\n      // We need better test coverage for this code path.\n      const xref: TypeXRefInternal = {\n        name: type.name,\n        path,\n        type: \"internal\",\n      };\n      return this.addTypeArguments(type, [xref]);\n    }\n  }\n\n  reference(type: ReferenceType): Type {\n    // if we got a reflection use that. It's not all that clear how to deal\n    // with type arguments here though...\n    const res = this.convertPrivateReferenceToReflection(type);\n    if (res) {\n      return res;\n    }\n    if (type.isIntentionallyBroken()) {\n      // If it's intentionally broken, don't add an xref. It's probably a type\n      // parameter.\n      return this.addTypeArguments(type, [type.name]);\n    } else {\n      return this.convertReferenceToXRef(type);\n    }\n  }\n  reflection(type: ReflectionType): Type {\n    if (type.declaration.kindOf(ReflectionKind.TypeLiteral)) {\n      return this.convertTypeLiteral(type.declaration);\n    }\n    if (type.declaration.kindOf(ReflectionKind.Constructor)) {\n      const result = this.convertSignature(type.declaration.signatures![0]);\n      result.unshift(\"{new \");\n      result.push(\"}\");\n      return result;\n    }\n    if (type.declaration.kindOf(ReflectionKind.FunctionOrMethod)) {\n      return this.convertSignature(type.declaration.signatures![0]);\n    }\n    throw new Error(\"Not implemented\");\n  }\n  rest(type: RestType): Type {\n    return [\"...\", ...this.convert(type.elementType, TypeContext.restElement)];\n  }\n  templateLiteral(type: TemplateLiteralType): Type {\n    return [\n      \"`\",\n      type.head,\n      ...type.tail.flatMap(([type, text]) => {\n        return [\n          \"${\",\n          ...this.convert(type, TypeContext.templateLiteralElement),\n          \"}\",\n          text,\n        ];\n      }),\n      \"`\",\n    ];\n  }\n  tuple(type: TupleType): Type {\n    const result: Type = [];\n    for (const elt of type.elements) {\n      result.push(...this.convert(elt, TypeContext.tupleElement));\n      result.push(\", \");\n    }\n    result.pop();\n    result.unshift(\"[\");\n    result.push(\"]\");\n    return result;\n  }\n  namedTupleMember(type: NamedTupleMember): Type {\n    const result: Type = [`${type.name}${type.isOptional ? \"?\" : \"\"}: `];\n    result.push(...this.convert(type.element, TypeContext.tupleElement));\n    return result;\n  }\n  typeOperator(type: TypeOperatorType): Type {\n    return [\n      type.operator,\n      \" \",\n      ...this.convert(type.target, TypeContext.typeOperatorTarget),\n    ];\n  }\n  union(type: UnionType): Type {\n    const result: Type = [];\n    for (const elt of type.types) {\n      result.push(...this.convert(elt, TypeContext.unionElement));\n      result.push(\" | \");\n    }\n    result.pop();\n    return result;\n  }\n  unknown(type: UnknownType): Type {\n    // I'm not sure how we get here: generally nobody explicitly annotates\n    // unknown, maybe it's inferred sometimes?\n    return [type.name];\n  }\n  array(t: ArrayType): Type {\n    const res = this.convert(t.elementType, TypeContext.arrayElement);\n    res.push(\"[]\");\n    return res;\n  }\n\n  convertSignature(sig: SignatureReflection): Type {\n    const result: Type = [\"(\"];\n    for (const param of sig.parameters || []) {\n      result.push(param.name + \": \");\n      result.push(...(param.type?.visit(this) || []));\n      result.push(\", \");\n    }\n    if (sig.parameters?.length) {\n      result.pop();\n    }\n    result.push(\") => \");\n    if (sig.type) {\n      result.push(...sig.type.visit(this));\n    } else {\n      result.push(intrinsicType(\"void\"));\n    }\n    return result;\n  }\n\n  convertTypeLiteral(lit: DeclarationReflection): Type {\n    if (lit.signatures) {\n      return this.convertSignature(lit.signatures[0]);\n    }\n    const result: Type = [\"{ \"];\n    // lit.indexSignature for 0.25.x, lit.indexSignatures for 0.26.0 and later.\n    // @ts-ignore\n    const index_sig = lit.indexSignature ?? lit.indexSignatures?.[0];\n    if (index_sig) {\n      if (index_sig.parameters?.length !== 1) {\n        throw new Error(\"oops\");\n      }\n      const key = index_sig.parameters[0];\n      // There's no exact TypeContext for indexedAccess b/c typedoc doesn't\n      // render it like this. mappedParameter and mappedTemplate look quite\n      // similar:\n      // [k in mappedParam]: mappedTemplate\n      //  vs\n      // [k: keyType]: valueType\n      const keyType = this.convert(key.type!, TypeContext.mappedParameter);\n      const valueType = this.convert(\n        index_sig.type!,\n        TypeContext.mappedTemplate,\n      );\n      result.push(\"[\", key.name, \": \");\n      result.push(...keyType);\n      result.push(\"]\", \": \");\n      result.push(...valueType);\n      result.push(\"; \");\n    }\n    for (const child of lit.children || []) {\n      result.push(child.name);\n      if (child.flags.isOptional) {\n        result.push(\"?: \");\n      } else {\n        result.push(\": \");\n      }\n      result.push(...(child.type?.visit(this) || []));\n      result.push(\"; \");\n    }\n    result.push(\"}\");\n    return result;\n  }\n}\n\nexport function convertType(\n  basePath: string,\n  reflToPath: ReadonlyMap<\n    DeclarationReflection | SignatureReflection,\n    string[]\n  >,\n  symbolToType: ReadonlySymbolToType,\n  type: SomeType,\n  context: TypeContext = TypeContext.none,\n): Type {\n  const typeConverter = new TypeConverter(basePath, reflToPath, symbolToType);\n  return typeConverter.convert(type, context);\n}\n\nexport function convertTypeLiteral(\n  basePath: string,\n  reflToPath: ReadonlyMap<\n    DeclarationReflection | SignatureReflection,\n    string[]\n  >,\n  symbolToType: ReadonlySymbolToType,\n  type: DeclarationReflection,\n): Type {\n  const typeConverter = new TypeConverter(basePath, reflToPath, symbolToType);\n  return typeConverter.convertTypeLiteral(type);\n}\n\nexport function referenceToXRef(\n  basePath: string,\n  reflToPath: ReadonlyMap<\n    DeclarationReflection | SignatureReflection,\n    string[]\n  >,\n  symbolToType: ReadonlySymbolToType,\n  type: ReferenceType,\n): Type {\n  const converter = new TypeConverter(basePath, reflToPath, symbolToType);\n  return converter.convertReferenceToXRef(type);\n}\n"
  },
  {
    "path": "sphinx_js/js/importHooks.mjs",
    "content": "async function tryResolve(specifier, context, nextResolve) {\n  try {\n    return await nextResolve(specifier, context);\n  } catch (e) {\n    if (e.code !== \"ERR_MODULE_NOT_FOUND\") {\n      // Unusual error let it propagate\n      throw e;\n    }\n  }\n}\n\n// An import hook to pick up packages in the node_modules that typedoc is\n// installed into\nexport async function resolve(specifier, context, nextResolve) {\n  // Take an `import` or `require` specifier and resolve it to a URL.\n  const origURL = context.parentURL;\n  const fallbackURL = `file:${process.env[\"TYPEDOC_NODE_MODULES\"]}/`;\n  for (const parentURL of [origURL, fallbackURL]) {\n    context.parentURL = parentURL;\n    const res = await tryResolve(specifier, context, nextResolve);\n    context.parentURL = origURL;\n    if (res) {\n      return res;\n    }\n  }\n  // If we get here, this will throw an error.\n  return nextResolve(specifier, context);\n}\n"
  },
  {
    "path": "sphinx_js/js/ir.ts",
    "content": "// Define the types for our IR. Must match the cattrs+json serialization\n// format from ir.py\n\nexport type TypeXRefIntrinsic = {\n  name: string;\n  type: \"intrinsic\";\n};\n\nexport function intrinsicType(name: string): TypeXRefIntrinsic {\n  return {\n    name,\n    type: \"intrinsic\",\n  };\n}\n\nexport type TypeXRefInternal = {\n  name: string;\n  path: string[];\n  type: \"internal\";\n};\n\nexport type TypeXRefExternal = {\n  name: string;\n  package: string;\n  sourcefilename: string | null;\n  qualifiedName: string | null;\n  type: \"external\";\n};\n\nexport type TypeXRef = TypeXRefExternal | TypeXRefInternal | TypeXRefIntrinsic;\nexport type Type = (string | TypeXRef)[];\n\nexport type DescriptionName = {\n  text: string;\n  type: \"name\";\n};\n\nexport type DescriptionText = {\n  text: string;\n  type: \"text\";\n};\n\nexport type DescriptionCode = {\n  code: string;\n  type: \"code\";\n};\n\nexport type DescriptionItem =\n  | DescriptionName\n  | DescriptionText\n  | DescriptionCode;\nexport type Description = DescriptionItem[];\n\nexport type Pathname = string[];\n\nexport type NoDefault = { _no_default: true };\nexport const NO_DEFAULT: NoDefault = { _no_default: true };\n\nexport type Member = {\n  is_abstract: boolean;\n  is_optional: boolean;\n  is_static: boolean;\n  is_private: boolean;\n};\n\nexport type TypeParam = {\n  name: string;\n  extends: Type | null;\n  description: Description;\n};\n\nexport type Param = {\n  name: string;\n  description: Description;\n  is_variadic: boolean;\n  has_default: boolean;\n  default: string | NoDefault;\n  type: Type;\n};\n\nexport type Return = {\n  type: Type;\n  description: Description;\n};\n\nexport type Module = {\n  filename: string;\n  deppath: string;\n  path: Pathname;\n  line: number;\n  attributes: TopLevel[];\n  functions: IRFunction[];\n  classes: Class[];\n};\n\nexport type TopLevel = {\n  name: string;\n  path: Pathname;\n  filename: string;\n  deppath: string;\n  description: Description;\n  modifier_tags: string[];\n  block_tags: { [key: string]: Description[] };\n  line: number | null;\n  deprecated: Description | boolean;\n  examples: Description[];\n  see_alsos: string[];\n  properties: Attribute[];\n  exported_from: Pathname | null;\n  documentation_root: boolean;\n};\n\nexport type Attribute = TopLevel &\n  Member & {\n    type: Type;\n    readonly: boolean;\n    kind: \"attribute\";\n  };\n\nexport type IRFunction = TopLevel &\n  Member & {\n    is_async: boolean;\n    params: Param[];\n    returns: Return[];\n    type_params: TypeParam[];\n    kind: \"function\";\n    exceptions: never[];\n  };\n\nexport type _MembersAndSupers = {\n  members: (IRFunction | Attribute)[];\n  supers: Type[];\n};\n\nexport type Interface = TopLevel &\n  _MembersAndSupers & {\n    type_params: TypeParam[];\n    kind: \"interface\";\n  };\n\nexport type Class = TopLevel &\n  _MembersAndSupers & {\n    constructor_: IRFunction | null;\n    is_abstract: boolean;\n    interfaces: Type[];\n    type_params: TypeParam[];\n    kind: \"class\";\n  };\n\nexport type TypeAlias = TopLevel & {\n  kind: \"typeAlias\";\n  type: Type;\n  type_params: TypeParam[];\n};\n\nexport type TopLevelIR = Attribute | IRFunction | Class | Interface | TypeAlias;\n"
  },
  {
    "path": "sphinx_js/js/main.ts",
    "content": "import { writeFile } from \"fs/promises\";\nimport { ExitError, run } from \"./cli.ts\";\n\nasync function main() {\n  const start = Date.now();\n  const args = process.argv.slice(2);\n  let app, result;\n  try {\n    [app, result] = await run(args);\n  } catch (e) {\n    if (e instanceof ExitError) {\n      return e.code;\n    }\n    throw e;\n  }\n  const space = app.options.getValue(\"pretty\") ? \"\\t\" : \"\";\n  const res = JSON.stringify([result, app.extraData], null, space);\n  const json = app.options.getValue(\"json\");\n  await writeFile(json, res);\n  app.logger.info(`JSON written to ${json}`);\n  app.logger.verbose(`JSON rendering took ${Date.now() - start}ms`);\n  return 0;\n}\n\nprocess.exit(await main());\n"
  },
  {
    "path": "sphinx_js/js/package.json",
    "content": "{\n  \"name\": \"sphinx_js\",\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"tsx\": \"^4.9.0\",\n    \"typedoc\": \"^0.25.13\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.12.7\"\n  }\n}\n"
  },
  {
    "path": "sphinx_js/js/redirectPrivateAliases.ts",
    "content": "/**\n * This is very heavily inspired by typedoc-plugin-missing-exports.\n *\n * The goal isn't to document the missing exports, but rather to remove them\n * from the documentation of actually exported stuff. If someone says:\n *\n * ```\n * type MyPrivateAlias = ...\n *\n * function f(a: MyPrivateAlias) {\n *\n * }\n * ```\n *\n * Then the documentation for f should document the value of MyPrivateAlias. We\n * create a ReflectionType for each missing export and stick them in a\n * SymbolToType map which we add to the application. In renderType.ts, if we\n * have a reference type we check if it's in the SymbolToType map and if so we\n * can use the reflection in place of the reference.\n *\n * More or less unrelatedly, we also add the --sphinxJsConfig option to the\n * options parser so we can pass the sphinxJsConfig on the command line.\n */\nimport {\n  Application,\n  Context,\n  Converter,\n  DeclarationReflection,\n  ProjectReflection,\n  ReferenceType,\n  Reflection,\n  ReflectionKind,\n  SomeType,\n} from \"typedoc\";\nimport ts from \"typescript\";\n\n// Map from the Symbol that is the target of the broken reference to the type\n// reflection that it should be replaced by. Depending on whether the reference\n// type holds a symbolId or a reflection, we use fileName:position or\n// fileName:symbolName as the key (respectively). We could always use the\n// symbolName but the position is more specific.\ntype SymbolToTypeKey = `${string}:${number}` | `${string}:${string}`;\nexport type SymbolToType = Map<SymbolToTypeKey, SomeType>;\nexport type ReadonlySymbolToType = ReadonlyMap<SymbolToTypeKey, SomeType>;\n\nconst ModuleLike: ReflectionKind =\n  ReflectionKind.Project | ReflectionKind.Module;\n\nfunction getOwningModule(context: Context): Reflection {\n  let refl = context.scope;\n  // Go up the reflection hierarchy until we get to a module\n  while (!refl.kindOf(ModuleLike)) {\n    refl = refl.parent!;\n  }\n  return refl;\n}\n\n/**\n * @param app The typedoc app\n * @returns The type reference redirect table to be used in renderType.ts\n */\nexport function redirectPrivateTypes(app: Application): ReadonlySymbolToType {\n  const referencedSymbols = new Map<Reflection, Set<ts.Symbol>>();\n  const knownPrograms = new Map<Reflection, ts.Program>();\n  const symbolToType: SymbolToType = new Map<`${string}:${number}`, SomeType>();\n  app.converter.on(\n    Converter.EVENT_CREATE_DECLARATION,\n    (context: Context, refl: Reflection) => {\n      // TypeDoc 0.26 doesn't fire EVENT_CREATE_DECLARATION for project\n      // We need to ensure the project has a program attached to it, so\n      // do that when the first declaration is created.\n      if (knownPrograms.size === 0) {\n        knownPrograms.set(refl.project, context.program);\n      }\n      if (refl.kindOf(ModuleLike)) {\n        knownPrograms.set(refl, context.program);\n      }\n    },\n  );\n\n  const tsdocVersion = app.toString().split(\" \")[1];\n  let is28: boolean;\n  if (\n    tsdocVersion.startsWith(\"0.25\") ||\n    tsdocVersion.startsWith(\"0.26\") ||\n    tsdocVersion.startsWith(\"0.27\")\n  ) {\n    is28 = false;\n  } else if (tsdocVersion.startsWith(\"0.28\")) {\n    is28 = true;\n  } else {\n    throw new Error(`Typedoc version ${tsdocVersion} not supported`);\n  }\n\n  let getReflectionFromSymbol = is28\n    ? // @ts-ignore\n      (context: Context, s: ts.Symbol) => context.getReflectionFromSymbol(s)\n    : (context: Context, s: ts.Symbol) =>\n        // @ts-ignore\n        context.project.getReflectionFromSymbol(s);\n\n  /**\n   * Get the set of ts.symbols referenced from a ModuleReflection or\n   * ProjectReflection if there is only one file.\n   */\n  function getReferencedSymbols(owningModule: Reflection): Set<ts.Symbol> {\n    let set = referencedSymbols.get(owningModule);\n    if (set) {\n      return set;\n    }\n    set = new Set();\n    referencedSymbols.set(owningModule, set);\n    return set;\n  }\n\n  function discoverMissingExports(\n    owningModule: Reflection,\n    context: Context,\n  ): ts.Symbol[] {\n    // An export is missing if it was referenced and is not contained in the\n    // documented\n    const referenced = getReferencedSymbols(owningModule);\n    return Array.from(referenced).filter((s) => {\n      const refl = getReflectionFromSymbol(context, s);\n      return (\n        !refl ||\n        refl.flags.isPrivate ||\n        refl?.comment?.modifierTags.has(\"@hidden\")\n      );\n    });\n  }\n\n  // @ts-ignore\n  const patchTarget: {\n    createSymbolReference: (\n      symbol: ts.Symbol,\n      context: Context,\n      name: string,\n    ) => ReferenceType;\n  } = is28 ? Context.prototype : ReferenceType;\n\n  const origCreateSymbolReference = patchTarget.createSymbolReference;\n  patchTarget.createSymbolReference = function (\n    symbol: ts.Symbol,\n    context: Context,\n    name: string,\n  ) {\n    const owningModule = getOwningModule(context);\n    getReferencedSymbols(owningModule).add(symbol);\n    return origCreateSymbolReference.call(this, symbol, context, name);\n  };\n\n  function onResolveBegin(context: Context): void {\n    const modules: (DeclarationReflection | ProjectReflection)[] =\n      context.project.getChildrenByKind(ReflectionKind.Module);\n    if (modules.length === 0) {\n      // Single entry point, just target the project.\n      modules.push(context.project);\n    }\n\n    for (const mod of modules) {\n      const program = knownPrograms.get(mod);\n      if (!program) continue;\n\n      // Nasty hack here that will almost certainly break in future TypeDoc versions.\n      context.setActiveProgram(program);\n\n      const missing = discoverMissingExports(mod, context);\n      for (const name of missing) {\n        const decl = name.declarations![0];\n        if (decl.getSourceFile().fileName.includes(\"node_modules\")) {\n          continue;\n        }\n        // TODO: maybe handle things other than TypeAliases?\n        if (ts.isTypeAliasDeclaration(decl)) {\n          const sf = decl.getSourceFile();\n          const fileName = sf.fileName;\n          const converted = context.converter.convertType(context, decl.type);\n          // Ideally we should be able to key on position rather than file and\n          // name but I couldn't figure out how.\n          symbolToType.set(`${fileName}:${decl.name.getText()}`, converted);\n        }\n      }\n      context.setActiveProgram(void 0);\n    }\n  }\n\n  app.converter.on(Converter.EVENT_RESOLVE_BEGIN, onResolveBegin);\n  return symbolToType;\n}\n"
  },
  {
    "path": "sphinx_js/js/registerImportHook.mjs",
    "content": "import { register } from \"node:module\";\n\nregister(\"./importHooks.mjs\", import.meta.url);\n"
  },
  {
    "path": "sphinx_js/js/sphinxJsConfig.ts",
    "content": "import {\n  Application,\n  DeclarationReflection,\n  ParameterReflection,\n  ProjectReflection,\n} from \"typedoc\";\nimport { TopLevel } from \"./ir.ts\";\n\nexport type SphinxJsConfig = {\n  shouldDestructureArg?: (param: ParameterReflection) => boolean;\n  preConvert?: (app: Application) => Promise<void>;\n  postConvert?: (\n    app: Application,\n    project: ProjectReflection,\n    typedocToIRMap: ReadonlyMap<DeclarationReflection, TopLevel>,\n  ) => Promise<void>;\n};\n"
  },
  {
    "path": "sphinx_js/js/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"noEmit\": true,\n    \"allowImportingTsExtensions\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "sphinx_js/js/typedocPatches.ts",
    "content": "/** Declare some extra stuff we monkeypatch on to typedoc */\ndeclare module \"typedoc\" {\n  export interface TypeDocOptionMap {\n    sphinxJsConfig: string;\n  }\n  export interface Application {\n    extraData: {\n      [key: string]: any;\n    };\n  }\n}\n"
  },
  {
    "path": "sphinx_js/js/typedocPlugin.ts",
    "content": "/**\n * Typedoc plugin which adds --sphinxJsConfig option\n */\n\n// TODO: we don't seem to resolve imports correctly in this file, but it works\n// to do a dynamic import. Figure out why.\n\nexport async function load(app: any): Promise<void> {\n  // @ts-ignore\n  const typedoc = await import(\"typedoc\");\n  app.options.addDeclaration({\n    name: \"sphinxJsConfig\",\n    help: \"[typedoc-plugin-sphinx-js]: the sphinx-js config\",\n    type: typedoc.ParameterType.String,\n  });\n}\n"
  },
  {
    "path": "sphinx_js/jsdoc.py",
    "content": "\"\"\"JavaScript analyzer\n\nAnalyzers run jsdoc or typedoc or whatever, squirrel away their output, and\nthen lazily constitute IR objects as requested.\n\n\"\"\"\n\nimport pathlib\nimport subprocess\nfrom collections import defaultdict\nfrom collections.abc import Callable, Sequence\nfrom errno import ENOENT\nfrom json import dumps, load\nfrom os.path import join, normpath, relpath, sep, splitext\nfrom tempfile import TemporaryFile\nfrom typing import Any, Literal, TypedDict\n\nfrom sphinx.application import Sphinx\nfrom sphinx.errors import SphinxError\n\nfrom .analyzer_utils import (\n    Command,\n    cache_to_file,\n    is_explicitly_rooted,\n    search_node_modules,\n)\nfrom .ir import (\n    NO_DEFAULT,\n    Attribute,\n    Class,\n    DescriptionCode,\n    Exc,\n    Function,\n    Param,\n    Pathname,\n    Return,\n    TopLevel,\n)\nfrom .parsers import PathVisitor, path_and_formal_params\nfrom .suffix_tree import SuffixTree\n\n\nclass JsDocCode(TypedDict, total=False):\n    paramnames: list[str]\n\n\nclass Meta(TypedDict, total=False):\n    path: str\n    filename: str\n    lineno: int\n    code: JsDocCode\n\n\nclass JsdocType(TypedDict, total=False):\n    names: list[str]\n\n\nclass Doclet(TypedDict, total=False):\n    name: str\n    comment: str\n    undocumented: bool\n    access: str\n    scope: str\n    meta: Meta\n    longname: str\n    memberof: str\n    description: str\n    type: JsdocType\n    classdesc: str\n    exceptions: list[\"Doclet\"]\n    returns: list[\"Doclet\"]\n    examples: list[Any]\n    see_alsos: list[Any]\n    properties: list[\"Doclet\"]\n    params: list[\"Doclet\"]\n    variable: bool\n\n\nclass Analyzer:\n    \"\"\"A runner of a langauge-specific static analysis tool and translator of\n    the results to our IR\n\n    \"\"\"\n\n    def __init__(self, json: list[Doclet], base_dir: str):\n        \"\"\"Index and squirrel away the JSON for later lazy conversion to IR\n        objects.\n\n        :arg json: The loaded JSON output from jsdoc\n        :arg base_dir: Resolve paths in the JSON relative to this directory.\n            This must be an absolute pathname.\n\n        \"\"\"\n        self._base_dir = base_dir\n        # 2 doclets are made for classes, and they are largely redundant: one\n        # for the class itself and another for the constructor. However, the\n        # constructor one gets merged into the class one and is intentionally\n        # marked as undocumented, even if it isn't. See\n        # https://github.com/jsdoc3/jsdoc/issues/1129.\n        doclets = [\n            doclet\n            for doclet in json\n            if doclet.get(\"comment\") and not doclet.get(\"undocumented\")\n        ]\n\n        # Build table for lookup by name, which most directives use:\n        self._doclets_by_path: SuffixTree[Doclet] = SuffixTree()\n        self._doclets_by_path.add_many(\n            (full_path_segments(d, base_dir), d) for d in doclets\n        )\n\n        # Build lookup table for autoclass's :members: option. This will also\n        # pick up members of functions (inner variables), but it will instantly\n        # filter almost all of them back out again because they're\n        # undocumented. We index these by unambiguous full path. Then, when\n        # looking them up by arbitrary name segment, we disambiguate that first\n        # by running it through the suffix tree above. Expect trouble due to\n        # jsdoc's habit of calling things (like ES6 class methods)\n        # \"<anonymous>\" in the memberof field, even though they have names.\n        # This will lead to multiple methods having each other's members. But\n        # if you don't have same-named inner functions or inner variables that\n        # are documented, you shouldn't have trouble.\n        self._doclets_by_class = defaultdict(lambda: [])\n        for d in doclets:\n            of = d.get(\"memberof\")\n            if of:  # speed optimization\n                segments = full_path_segments(d, base_dir, longname_field=\"memberof\")\n                self._doclets_by_class[tuple(segments)].append(d)\n\n    @classmethod\n    def from_disk(\n        cls, abs_source_paths: list[str], app: Sphinx, base_dir: str\n    ) -> \"Analyzer\":\n        json = jsdoc_output(\n            getattr(app.config, \"jsdoc_cache\", None),\n            abs_source_paths,\n            base_dir,\n            app.confdir,\n            getattr(app.config, \"jsdoc_config_path\", None),\n        )\n        return cls(json, base_dir)\n\n    def get_object(\n        self, path_suffix: list[str], as_type: Literal[\"function\", \"class\", \"attribute\"]\n    ) -> TopLevel:\n        \"\"\"Return the IR object with the given path suffix.\n\n        If helpful, use the ``as_type`` hint, which identifies which autodoc\n        directive the user called.\n\n        \"\"\"\n        # Design note: Originally, I had planned to eagerly convert all the\n        # doclets to the IR. But it's hard to tell unambiguously what kind\n        # each doclet is, at least in the case of jsdoc. If instead we lazily\n        # convert each doclet as it's referenced by an autodoc directive, we\n        # can use the hint we previously did: the user saying \"this is a\n        # function (by using autofunction on it)\", \"this is a class\", etc.\n        # Additionally, being lazy lets us avoid converting unused doclets\n        # altogether.\n        try:\n            doclet_as_whatever = {\n                \"function\": self._doclet_as_function,\n                \"class\": self._doclet_as_class,\n                \"attribute\": self._doclet_as_attribute,\n            }[as_type]\n        except KeyError:\n            raise NotImplementedError(\"Unknown autodoc directive: auto%s\" % as_type)\n\n        doclet, full_path = self._doclets_by_path.get_with_path(path_suffix)\n        return doclet_as_whatever(doclet, full_path)\n\n    def _doclet_as_class(self, doclet: Doclet, full_path: Sequence[str]) -> Class:\n        # This is an instance method so it can get at the base dir.\n        members: list[Function | Attribute] = []\n        for member_doclet in self._doclets_by_class[tuple(full_path)]:\n            kind = member_doclet.get(\"kind\")\n            member_full_path = full_path_segments(member_doclet, self._base_dir)\n            # Typedefs should still fit into function-shaped holes:\n            doclet_as_whatever: (\n                Callable[[Doclet, list[str]], Function]\n                | Callable[[Doclet, list[str]], Attribute]\n            ) = (\n                self._doclet_as_function\n                if (kind == \"function\" or kind == \"typedef\")\n                else self._doclet_as_attribute\n            )\n            member = doclet_as_whatever(member_doclet, member_full_path)\n            members.append(member)\n        return Class(\n            description=doclet.get(\"classdesc\", \"\"),\n            supers=[],  # Could implement for JS later.\n            exported_from=None,  # Could implement for JS later.\n            is_abstract=False,\n            interfaces=[],\n            # Right now, a class generates several doclets, all but one of\n            # which are marked as undocumented. In the one that's left, most of\n            # the fields are about the default constructor:\n            constructor_=self._doclet_as_function(doclet, full_path),\n            members=members,\n            **top_level_properties(doclet, full_path, self._base_dir),\n        )\n\n    def _doclet_as_function(self, doclet: Doclet, full_path: Sequence[str]) -> Function:\n        return Function(\n            description=description(doclet),\n            exported_from=None,\n            is_abstract=False,\n            is_optional=False,\n            is_static=is_static(doclet),\n            is_async=False,\n            is_private=is_private(doclet),\n            exceptions=exceptions_to_ir(doclet.get(\"exceptions\", [])),\n            returns=returns_to_ir(doclet.get(\"returns\", [])),\n            params=params_to_ir(doclet),\n            **top_level_properties(doclet, full_path, self._base_dir),\n        )\n\n    def _doclet_as_attribute(\n        self, doclet: Doclet, full_path: Sequence[str]\n    ) -> Attribute:\n        return Attribute(\n            description=description(doclet),\n            exported_from=None,\n            is_abstract=False,\n            is_optional=False,\n            is_static=False,\n            is_private=is_private(doclet),\n            type=get_type(doclet),\n            **top_level_properties(doclet, full_path, self._base_dir),\n        )\n\n\ndef is_private(doclet: Doclet) -> bool:\n    return doclet.get(\"access\") == \"private\"\n\n\ndef is_static(doclet: Doclet) -> bool:\n    return doclet.get(\"scope\") == \"static\"\n\n\ndef full_path_segments(\n    d: Doclet,\n    base_dir: str,\n    longname_field: Literal[\"longname\", \"memberof\"] = \"longname\",\n) -> list[str]:\n    \"\"\"Return the full, unambiguous list of path segments that points to an\n    entity described by a doclet.\n\n    Example: ``['./', 'dir/', 'dir/', 'file.', 'object.', 'object#', 'object']``\n\n    :arg d: The doclet\n    :arg base_dir: Absolutized value of the root_for_relative_js_paths option\n    :arg longname_field: The field to look in at the top level of the doclet\n        for the long name of the object to emit a path to\n    \"\"\"\n    meta = d[\"meta\"]\n    rel = relpath(meta[\"path\"], base_dir)\n    rel = \"/\".join(rel.split(sep))\n    rooted_rel = rel if is_explicitly_rooted(rel) else \"./%s\" % rel\n\n    # Building up a string and then parsing it back down again is probably\n    # not the fastest approach, but it means knowledge of path format is in\n    # one place: the parser.\n    path = \"{}/{}.{}\".format(\n        rooted_rel, splitext(meta[\"filename\"])[0], d[longname_field]\n    )\n    return PathVisitor().visit(  # type:ignore[no-any-return]\n        path_and_formal_params[\"path\"].parse(path)\n    )\n\n\n@cache_to_file(lambda cache, *args: cache)\ndef jsdoc_output(\n    cache: str | None,\n    abs_source_paths: list[str],\n    base_dir: str,\n    sphinx_conf_dir: str | pathlib.Path,\n    config_path: str | None = None,\n) -> list[Doclet]:\n    jsdoc = search_node_modules(\"jsdoc\", \"jsdoc/jsdoc.js\", sphinx_conf_dir)\n    command = Command(\"node\")\n    command.add(jsdoc)\n    command.add(\"-X\", *abs_source_paths)\n    if config_path:\n        command.add(\"-c\", normpath(join(sphinx_conf_dir, config_path)))\n\n    # Use a temporary file to handle large output volume. JSDoc defaults to\n    # utf8-encoded output.\n    with TemporaryFile(mode=\"w+b\") as temp:\n        try:\n            subprocess.run(\n                command.make(), cwd=sphinx_conf_dir, stdout=temp, encoding=\"utf8\"\n            )\n        except OSError as exc:\n            if exc.errno == ENOENT:\n                raise SphinxError(\n                    '%s was not found. Install it using \"npm install -g jsdoc\".'\n                    % command.program\n                )\n            else:\n                raise\n        # Once output is finished, move back to beginning of file and load it:\n        temp.seek(0)\n        try:\n            return load(temp)  # type:ignore[no-any-return]\n        except ValueError:\n            raise SphinxError(\n                \"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.\"\n                % abs_source_paths\n            )\n\n\ndef format_default_according_to_type_hints(\n    value: Any, declared_types: str | None, first_type_is_string: bool\n) -> Any:\n    \"\"\"Return the default value for a param, formatted as a string\n    ready to be used in a formal parameter list.\n\n    JSDoc is a mess at extracting default values. It can unambiguously\n    extract only a few simple types from the function signature, and\n    ambiguity is even more rife when extracting from doclets. So we use\n    any declared types to resolve the ambiguity.\n\n    :arg value: The extracted value, which may be of the right or wrong type\n    :arg declared_types: A list of types declared in the doclet for\n        this param. For example ``{string|number}`` would yield ['string',\n        'number'].\n    :arg first_type_is_string: Whether the first declared type for this param\n        is string, which we use as a signal that any string-typed default value\n        in the JSON is legitimately string-typed rather than some arrow\n        function or something just encased in quotes because they couldn't\n        think what else to do. Thus, if you want your ambiguously documented\n        default like ``@param {string|Array} [foo=[]]`` to be treated as a\n        string, make sure \"string\" comes first.\n\n    \"\"\"\n    if isinstance(value, str):  # JSDoc threw it to us as a string in the JSON.\n        if declared_types and not first_type_is_string:\n            # It's a spurious string, like ``() => 5`` or a variable name.\n            # Let it through verbatim.\n            return value\n        else:\n            # It's a real string.\n            return dumps(value)  # Escape any contained quotes.\n    else:  # It came in as a non-string.\n        if first_type_is_string:\n            # It came in as an int, null, or bool, and we have to\n            # convert it back to a string.\n            return f'\"{dumps(value)}\"'\n        else:\n            # It's fine as the type it is.\n            return dumps(value)\n\n\ndef description(obj: Doclet) -> str:\n    return obj.get(\"description\", \"\")\n\n\ndef get_type(props: Doclet) -> str | None:\n    \"\"\"Given an arbitrary object from a jsdoc-emitted JSON file, go get the\n    ``type`` property, and return the textual rendering of the type, possibly a\n    union like ``Foo | Bar``, or None if we don't know the type.\"\"\"\n    names = props.get(\"type\", {}).get(\"names\", [])\n    return \"|\".join(names) if names else None\n\n\ndef top_level_properties(\n    doclet: Doclet, full_path: Sequence[str], base_dir: str\n) -> dict[str, Any]:\n    \"\"\"Extract information common to complex entities, and return it as a dict.\n\n    Specifically, pull out the information needed to parametrize TopLevel's\n    constructor.\n\n    \"\"\"\n    return dict(\n        name=doclet[\"name\"],\n        path=Pathname(full_path),\n        filename=doclet[\"meta\"][\"filename\"],\n        deppath=relpath(\n            join(doclet[\"meta\"][\"path\"], doclet[\"meta\"][\"filename\"]), base_dir\n        ),\n        # description's source varies depending on whether the doclet is a\n        #    class, so it gets filled out elsewhere.\n        line=doclet[\"meta\"][\"lineno\"],\n        deprecated=doclet.get(\"deprecated\", False),\n        examples=[\n            [DescriptionCode(\"```js\\n\" + x + \"\\n```\")]\n            for x in doclet.get(\"examples\", [])\n        ],\n        see_alsos=doclet.get(\"see\", []),\n        properties=properties_to_ir(doclet.get(\"properties\", [])),\n    )\n\n\ndef properties_to_ir(properties: list[Doclet]) -> list[Attribute]:\n    \"\"\"Turn jsdoc-emitted properties JSON into a list of Properties.\"\"\"\n    return [\n        Attribute(\n            type=get_type(p),\n            name=p[\"name\"],\n            # We can get away with setting null values for these\n            # because we never use them for anything:\n            path=Pathname([]),\n            filename=\"\",\n            deppath=\"\",\n            description=description(p),\n            line=0,\n            deprecated=False,\n            examples=[],\n            see_alsos=[],\n            properties=[],\n            exported_from=None,\n            is_abstract=False,\n            is_optional=False,\n            is_static=False,\n            is_private=False,\n        )\n        for p in properties\n    ]\n\n\ndef first_type_is_string(type: JsdocType) -> bool:\n    type_names = type.get(\"names\", [])\n    return bool(type_names) and type_names[0] == \"string\"\n\n\ndef params_to_ir(doclet: Doclet) -> list[Param]:\n    \"\"\"Extract the parameters of a function or class, and return a list of\n    Param instances.\n\n    Formal param fallback philosophy:\n\n    1. If the user puts a formal param list in the RST explicitly, use that.\n    2. Else, if they've @param'd anything, show just those args. This gives the\n       user full control from the code, so they can use autoclass without\n       having to manually write each function signature in the RST.\n    3. Else, extract a formal param list from the meta field, which will lack\n       descriptions.\n\n    Param list:\n\n    * Don't show anything without a description or at least a type. It adds\n      nothing.\n\n    Our extraction to IR thus follows our formal param philosophy, and the\n    renderer caps it off by checking for descriptions and types while building\n    the param+description list.\n\n    :arg doclet: A JSDoc doclet representing a function or class\n\n    \"\"\"\n    ret = []\n\n    # First, go through the explicitly documented params:\n    for p in doclet.get(\"params\", []):\n        type = get_type(p)\n        default = p.get(\"defaultvalue\", NO_DEFAULT)\n        formatted_default = (\n            NO_DEFAULT\n            if default is NO_DEFAULT\n            else format_default_according_to_type_hints(\n                default, type, first_type_is_string(p.get(\"type\", {}))\n            )\n        )\n        ret.append(\n            Param(\n                name=p[\"name\"],\n                description=description(p),\n                has_default=default is not NO_DEFAULT,\n                default=formatted_default,\n                is_variadic=p.get(\"variable\", False),\n                type=get_type(p),\n            )\n        )\n\n    # Use params from JS code if there are no documented @params.\n    if not ret:\n        ret = [Param(name=p) for p in doclet[\"meta\"][\"code\"].get(\"paramnames\", [])]\n\n    return ret\n\n\ndef exceptions_to_ir(exceptions: list[Doclet]) -> list[Exc]:\n    \"\"\"Turn jsdoc's JSON-formatted exceptions into a list of Exceptions.\"\"\"\n    return [Exc(type=get_type(e), description=description(e)) for e in exceptions]\n\n\ndef returns_to_ir(returns: list[Doclet]) -> list[Return]:\n    return [Return(type=get_type(r), description=description(r)) for r in returns]\n"
  },
  {
    "path": "sphinx_js/parsers.py",
    "content": "from re import sub\n\nfrom parsimonious import Grammar, NodeVisitor\n\npath_and_formal_params = Grammar(\n    r\"\"\"\n    path_and_formal_params = path formal_params\n\n    # Invalid JS symbol names and wild-and-crazy placement of slashes later in\n    # the path (after the FS path is over) will be caught at name-resolution\n    # time.\n    #\n    # Note that \".\" is a non-separator char only when used in ./ and ../\n    # prefixes.\n    path = relative_dir* middle_segments name\n\n    # A name is a series of non-separator (not ~#/.), non-(, and backslashed\n    # characters.\n    name = ~r\"(?:[^(/#~.\\\\]|\\\\.)+\"\n\n    sep = ~r\"[#~/.]\"\n    relative_dir = \"./\" / \"../\"\n    middle_segments = name_and_sep*\n    name_and_sep = name sep\n    formal_params = ~r\".*\"\n    \"\"\"\n)\n\n\nclass PathVisitor(NodeVisitor):  # type:ignore[type-arg]\n    grammar = path_and_formal_params\n\n    def visit_path_and_formal_params(self, node, children):\n        return children\n\n    def visit_name(self, node, children):\n        # This, for better or worse, also makes Python string escape sequences,\n        # like \\n for newline, work.\n        return _backslash_unescape(node.text)\n\n    def visit_sep(self, node, children):\n        return node.text\n\n    def visit_path(self, node, children):\n        relative_dirs, middle_segments, name = children\n        segments = relative_dirs[:]\n        segments.extend(middle_segments)\n        segments.append(name)\n        return segments\n\n    def visit_name_and_sep(self, node, children):\n        \"\"\"Concatenate name and separator into one string.\"\"\"\n        return \"\".join(x for x in children)\n\n    def visit_formal_params(self, node, children):\n        return node.text\n\n    def visit_relative_dir(self, node, children):\n        \"\"\"Return './' or '../'.\"\"\"\n        return node.text\n\n    def visit_cur_dir(self, node, children):\n        return node.text\n\n    def visit_middle_segments(self, node, children):\n        return children\n\n    def generic_visit(self, node, visited_children):\n        \"\"\"``relative_dir*`` has already turned each item of ``children`` into\n        './' or '../'. Just pass the list of those through.\"\"\"\n        return visited_children\n\n\ndef _backslash_unescape(str):\n    \"\"\"Return a string with backslash escape sequences replaced with their\n    literal meanings.\n\n    Don't respect any of the conventional \\n, \\v, \\t, etc. Keep it simple. Keep\n    it safe.\n\n    \"\"\"\n    return sub(r\"\\\\(.)\", lambda match: match.group(1), str)\n"
  },
  {
    "path": "sphinx_js/py.typed",
    "content": ""
  },
  {
    "path": "sphinx_js/renderers.py",
    "content": "import textwrap\nfrom collections.abc import Callable, Iterable, Iterator, Sequence\nfrom functools import partial\nfrom re import sub\nfrom typing import Any, Literal, Protocol, TypeVar\n\nfrom docutils import nodes\nfrom docutils.nodes import Node\nfrom docutils.parsers.rst import Directive\nfrom docutils.parsers.rst import Parser as RstParser\nfrom docutils.statemachine import StringList\nfrom jinja2 import Environment, PackageLoader\nfrom sphinx import addnodes\nfrom sphinx import version_info as sphinx_version_info\nfrom sphinx.application import Sphinx\nfrom sphinx.config import Config\nfrom sphinx.errors import SphinxError\nfrom sphinx.ext.autosummary import autosummary_table, extract_summary\nfrom sphinx.util import logging, rst\nfrom sphinx.util.docutils import switch_source_input\n\nfrom sphinx_js import ir\n\nfrom .analyzer_utils import dotted_path\nfrom .ir import (\n    Attribute,\n    Class,\n    DescriptionName,\n    DescriptionText,\n    Exc,\n    Function,\n    Interface,\n    Module,\n    Param,\n    Pathname,\n    Return,\n    TopLevel,\n    Type,\n    TypeAlias,\n    TypeParam,\n    TypeXRef,\n    TypeXRefInternal,\n)\nfrom .jsdoc import Analyzer as JsAnalyzer\nfrom .parsers import PathVisitor\nfrom .suffix_tree import SuffixAmbiguous, SuffixNotFound\nfrom .typedoc import Analyzer as TsAnalyzer\n\nAnalyzer = TsAnalyzer | JsAnalyzer\n\nlogger = logging.getLogger(__name__)\n\n\ndef new_document_from_parent(\n    source_path: str, parent_doc: nodes.document, line: int | None = None\n) -> nodes.document:\n    \"\"\"Create a new document that inherits the parent's settings and reporter.\"\"\"\n    settings = parent_doc.settings\n    reporter = parent_doc.reporter\n    doc = nodes.document(settings, reporter, source=source_path)\n    doc.note_source(source_path, -1)\n    # Store line number for sphinx_js_type_role to use\n    doc.sphinx_js_source_line = line  # type: ignore[attr-defined]\n    return doc\n\n\ndef sort_attributes_first_then_by_path(obj: TopLevel) -> Any:\n    \"\"\"Return a sort key for IR objects.\"\"\"\n    match obj:\n        case Attribute(_):\n            idx = 0\n        case Function(_):\n            idx = 1\n        case Class(_) | Interface(_):\n            idx = 2\n\n    return idx, obj.path.segments\n\n\ndef _members_to_include_inner(\n    members: Iterable[TopLevel],\n    include: list[str],\n) -> list[TopLevel]:\n    \"\"\"Return the members that should be included (before excludes and\n    access specifiers are taken into account).\n\n    This will either be the ones explicitly listed after the\n    ``:members:`` option, in that order; all members of the class; or\n    listed members with remaining ones inserted at the placeholder \"*\".\n\n    \"\"\"\n    if not include:\n        # Specifying none means listing all.\n        return sorted(members, key=sort_attributes_first_then_by_path)\n    included_set = set(include)\n\n    # If the special name * is included in the list, include all other\n    # members, in sorted order.\n    if \"*\" in included_set:\n        star_index = include.index(\"*\")\n        sorted_not_included_members = sorted(\n            (m for m in members if m.name not in included_set),\n            key=sort_attributes_first_then_by_path,\n        )\n        not_included = [m.name for m in sorted_not_included_members]\n        include = include[:star_index] + not_included + include[star_index + 1 :]\n        included_set.update(not_included)\n\n    # Even if there are 2 members with the same short name (e.g. a\n    # static member and an instance one), keep them both. This\n    # prefiltering step should make the below sort less horrible, even\n    # though I'm calling index().\n    included_members = [m for m in members if m.name in included_set]\n    # sort()'s stability should keep same-named members in the order\n    # JSDoc spits them out in.\n    included_members.sort(key=lambda m: include.index(m.name))\n    return included_members\n\n\ndef members_to_include(\n    members: Iterable[TopLevel],\n    include: list[str],\n    exclude: list[str],\n    should_include_private: bool,\n) -> Iterator[TopLevel]:\n    for member in _members_to_include_inner(members, include):\n        if member.name in exclude:\n            continue\n        if not should_include_private and getattr(member, \"is_private\", False):\n            continue\n        yield member\n\n\ndef unwrapped(text: str) -> str:\n    \"\"\"Return the text with line wrapping removed.\"\"\"\n    return sub(r\"[ \\t]*[\\r\\n]+[ \\t]*\", \" \", text)\n\n\ndef render_description(description: ir.Description) -> str:\n    \"\"\"Construct a single comment string from a fancy object.\"\"\"\n    if isinstance(description, str):\n        return description\n    content = []\n    prev = \"\"\n    for s in description:\n        if isinstance(s, DescriptionName):\n            prev = s.text\n            content.append(prev + \"\\n\")\n            continue\n        if isinstance(s, DescriptionText):\n            prev = s.text\n            content.append(prev)\n            continue\n        # code\n        if s.code.startswith(\"```\") and s.code.count(\"\\n\") >= 1:\n            # A code pen\n            first_line, rest = s.code.split(\"\\n\", 1)\n            rest = rest.removesuffix(\"```\")\n            code_type = first_line.removeprefix(\"```\")\n            start = f\".. code-block:: {code_type}\\n\\n\"\n            codeblock = textwrap.indent(rest, \" \" * 4)\n            end = \"\\n\\n\"\n            content.append(\"\\n\" + start + codeblock + end)\n            continue\n\n        if s.code.startswith(\"``\"):\n            # Sphinx-style escaped, leave it alone.\n            content.append(s.code)\n            continue\n        if prev.endswith(\":\"):\n            # A sphinx role, leave it alone\n            content.append(s.code)\n            continue\n\n        if prev.endswith(\" \") and not s.code.endswith(\">`\"):\n            # Used single uptick with code, put double upticks\n            content.append(f\"`{s.code}`\")\n            continue\n        content.append(s.code)\n    return \"\".join(content)\n\n\nR = TypeVar(\"R\", bound=\"Renderer\")\n\n\nclass HasDepPath(Protocol):\n    deppath: str | None\n\n\nclass Renderer:\n    _type_xref_formatter: Callable[[TypeXRef], str]\n    # We turn the <span class=\"sphinx_js-type\"> in the analyzer tests because it\n    # makes a big mess.\n    _add_span: bool\n    _partial_path: list[str]\n    _explicit_formal_params: str\n    _content: list[str] | StringList\n    _options: dict[str, Any]\n\n    def _parse_path(self, arg: str) -> None:\n        # content, arguments, options, app: all need to be accessible to\n        # template_vars, so we bring them in on construction and stow them away\n        # on the instance so calls to template_vars don't need to concern\n        # themselves with what it needs.\n        (\n            self._partial_path,\n            self._explicit_formal_params,\n        ) = PathVisitor().parse(arg)\n\n    def __init__(\n        self,\n        directive: Directive,\n        app: Sphinx,\n        arguments: list[str],\n        content: list[str] | StringList | None = None,\n        options: dict[str, Any] | None = None,\n    ):\n        self._add_span = True\n        # Fix crash when calling eval_rst with CommonMarkParser:\n        if not hasattr(directive.state.document.settings, \"tab_width\"):\n            directive.state.document.settings.tab_width = 8\n\n        self._directive = directive\n        self._app = app\n        self._set_type_xref_formatter(app.config.ts_type_xref_formatter)\n        self._parse_path(arguments[0])\n        self._content = content or StringList()\n        self._options = options or {}\n\n    @classmethod\n    def from_directive(cls: type[R], directive: Directive, app: Sphinx) -> R:\n        \"\"\"Return one of these whose state is all derived from a directive.\n\n        This is suitable for top-level calls but not for when a renderer is\n        being called from a different renderer, lest content and such from the\n        outer directive be duplicated in the inner directive.\n\n        :arg directive: The associated Sphinx directive\n        :arg app: The Sphinx global app object. Some methods need this.\n\n        \"\"\"\n        return cls(\n            directive,\n            app,\n            arguments=directive.arguments,\n            content=directive.content,\n            options=directive.options,\n        )\n\n    def _set_type_xref_formatter(\n        self, formatter: Callable[[Config, TypeXRef], str] | None\n    ) -> None:\n        if formatter:\n            self._type_xref_formatter = partial(formatter, self._app.config)\n            return\n\n        def default_type_xref_formatter(xref: TypeXRef) -> str:\n            return xref.name\n\n        self._type_xref_formatter = default_type_xref_formatter\n\n    def get_object(self) -> HasDepPath:\n        raise NotImplementedError\n\n    def dependencies(self) -> set[str]:\n        \"\"\"Return a set of path(s) to the file(s) that the IR object\n        rendered by this renderer is from.  Each path is absolute or\n        relative to `root_for_relative_js_paths`.\n\n        \"\"\"\n        try:\n            obj = self.get_object()\n            if obj.deppath:\n                return set([obj.deppath])\n        except SphinxError as exc:\n            logger.exception(\"Exception while retrieving paths for IR object: %s\" % exc)\n        return set([])\n\n    def rst_nodes(self) -> list[Node]:\n        raise NotImplementedError\n\n\nclass JsRenderer(Renderer):\n    \"\"\"Abstract superclass for renderers of various sphinx-js directives\n\n    Provides an inversion-of-control framework for rendering and bridges us\n    from the hidden, closed-over JsDirective subclasses to top-level classes\n    that can see and use each other. Handles parsing of a single, all-consuming\n    argument that consists of a JS/TS entity reference and an optional formal\n    parameter list.\n\n    \"\"\"\n\n    _renderer_type: Literal[\"function\", \"class\", \"attribute\"]\n    _template: str\n\n    def _template_vars(self, name: str, obj: TopLevel) -> dict[str, Any]:\n        raise NotImplementedError\n\n    def lookup_object(\n        self,\n        partial_path: list[str],\n        renderer_type: Literal[\"function\", \"class\", \"attribute\"] = \"attribute\",\n    ) -> TopLevel:\n        try:\n            analyzer: Analyzer = (\n                self._app._sphinxjs_analyzer  # type:ignore[attr-defined]\n            )\n            obj = analyzer.get_object(partial_path, renderer_type)\n            return obj\n        except SuffixNotFound as exc:\n            raise SphinxError(\n                'No documentation was found for object \"%s\" or any path ending with that.'\n                % \"\".join(exc.segments)\n            )\n        except SuffixAmbiguous as exc:\n            raise SphinxError(\n                'More than one object matches the path suffix \"{}\". Candidate paths have these segments in front: {}'.format(\n                    \"\".join(exc.segments), exc.next_possible_keys\n                )\n            )\n\n    def get_object(self) -> TopLevel:\n        \"\"\"Return the IR object rendered by this renderer.\"\"\"\n        return self.lookup_object(self._partial_path, self._renderer_type)\n\n    def rst_nodes(self) -> list[Node]:\n        \"\"\"Render into RST nodes a thing shaped like a function, having a name\n        and arguments.\n\n        Fill in args, docstrings, and info fields from stored JSDoc output.\n\n        \"\"\"\n        obj = self.get_object()\n        rst = self.rst(\n            self._partial_path, obj, use_short_name=\"short-name\" in self._options\n        )\n\n        # Parse the RST into docutils nodes with a fresh doc, and return\n        # them. Use the directive's source location for error messages.\n        source, line = self._directive.state_machine.get_source_and_line(\n            self._directive.lineno\n        )\n        doc = new_document_from_parent(\n            source or \"\", self._directive.state.document, line\n        )\n        RstParser().parse(rst, doc)\n        return doc.children\n\n    def rst_for(self, obj: TopLevel) -> str:\n        renderer_class: type\n        match obj:\n            case Attribute(_) | TypeAlias(_):\n                renderer_class = AutoAttributeRenderer\n            case Function(_):\n                renderer_class = AutoFunctionRenderer\n            case Class(_) | Interface(_):\n                renderer_class = AutoClassRenderer\n            case _:\n                raise RuntimeError(\"This shouldn't happen...\")\n        renderer = renderer_class(\n            self._directive, self._app, arguments=[\"dummy\"], options={\"members\": [\"*\"]}\n        )\n        return renderer.rst([obj.name], obj, use_short_name=False)\n\n    def rst(\n        self, partial_path: list[str], obj: TopLevel, use_short_name: bool = False\n    ) -> str:\n        \"\"\"Return rendered RST about an entity with the given name and IR\n        object.\"\"\"\n        dotted_name = partial_path[-1] if use_short_name else dotted_path(partial_path)\n\n        # Render to RST using Jinja:\n        env = Environment(loader=PackageLoader(\"sphinx_js\", \"templates\"))\n        template = env.get_template(self._template)\n        result = template.render(**self._template_vars(dotted_name, obj))\n        result = result.strip()\n        had_blank = False\n        lines = []\n        for line in result.splitlines():\n            if line.strip():\n                had_blank = False\n                lines.append(line.rstrip())\n            elif not had_blank:\n                lines.append(\"\")\n                had_blank = True\n        result = \"\\n\".join(lines) + \"\\n\"\n        return result\n\n    def _type_params(self, obj: Function | Class | TypeAlias | Interface) -> str:\n        if not obj.type_params:\n            return \"\"\n        return \"<{}>\".format(\", \".join(tp.name for tp in obj.type_params))\n\n    def _formal_params(self, obj: Function) -> str:\n        \"\"\"Return the JS function params, looking first to any explicit params\n        written into the directive and falling back to those in comments or JS\n        code.\n\n        Return a ReST-escaped string ready for substitution into the template.\n\n        \"\"\"\n        if self._explicit_formal_params:\n            return self._explicit_formal_params\n\n        formals = []\n        used_names = set()\n\n        for param in obj.params:\n            # Turn \"@param p2.subProperty\" into just p2. We wouldn't want to\n            # add subproperties to the flat formal param list:\n            name = param.name.split(\".\")[0]\n\n            # Add '...' to the parameter name if it's a variadic argument\n            if param.is_variadic:\n                name = \"...\" + name\n\n            if name not in used_names:\n                # We don't rst.escape() anything here, because, empirically,\n                # the js:function directive (or maybe directive params in\n                # general) automatically ignores markup constructs in its\n                # parameter (though not its contents).\n                formals.append(\n                    name if not param.has_default else f\"{name}={param.default}\"\n                )\n                used_names.add(name)\n\n        return \"({})\".format(\", \".join(formals))\n\n    def render_type(self, type: Type, escape: bool = False, bold: bool = True) -> str:\n        if not type:\n            return \"\"\n        if isinstance(type, str):\n            if bold:\n                type = \"**%s**\" % type\n            if escape:\n                type = rst.escape(type)\n            return type\n        it = iter(type)\n\n        def strs() -> Iterator[str]:\n            for elem in it:\n                if isinstance(elem, str):\n                    yield elem\n                else:\n                    xref.append(elem)\n                    return\n\n        res = []\n        while True:\n            xref: list[TypeXRef] = []\n            s = \"\".join(strs())\n            if escape:\n                s = rst.escape(s)\n            if s:\n                res.append(s)\n            if not xref:\n                break\n            res.append(self.render_xref(xref[0], escape))\n\n        joined = r\"\\ \".join(res)\n        if self._add_span:\n            return f\":sphinx_js_type:`{rst.escape(joined)}`\"\n        return joined\n\n    def render_xref(self, s: TypeXRef, escape: bool = False) -> str:\n        obj = None\n        if isinstance(s, TypeXRefInternal):\n            try:\n                obj = self.lookup_object(s.path)\n                # Stick the kind on the xref so that the formatter will know what\n                # xref role to emit. I'm not sure how to compute this earlier. It's\n                # convenient to do it here.\n                s.kind = type(obj).__name__.lower()\n            except SphinxError:\n                # This sometimes happens on the code path in\n                # convertReferenceToXRef when we generate an xref internal from\n                # a symbolId. That code path is probably entirely wrong.\n                # TODO: fix and add test coverage.\n                pass\n        result = self._type_xref_formatter(s)\n        if escape:\n            result = rst.escape(result)\n        return result\n\n    def _return_formatter(self, return_: Return) -> tuple[list[str], str]:\n        \"\"\"Derive heads and tail from ``@returns`` blocks.\"\"\"\n        tail = []\n        if return_.type:\n            tail.append(self.render_type(return_.type, escape=False))\n        if return_.description:\n            tail.append(render_description(return_.description))\n        return [\"returns\"], \" -- \".join(tail)\n\n    def _type_param_formatter(self, tparam: TypeParam) -> tuple[list[str], str] | None:\n        v = tparam.name\n        descr = render_description(tparam.description)\n        if tparam.extends:\n            descr += \" (extends \" + self.render_type(tparam.extends) + \")\"\n        heads = [\"typeparam\", v]\n        return heads, descr\n\n    def _param_formatter(self, param: Param) -> tuple[list[str], str] | None:\n        \"\"\"Derive heads and tail from ``@param`` blocks.\"\"\"\n        if not param.type and not param.description:\n            # There's nothing worth saying about this param.\n            return None\n        heads = [\"param\"]\n        heads.append(param.name)\n\n        tail = render_description(param.description)\n        return heads, tail\n\n    def _param_type_formatter(self, param: Param) -> tuple[list[str], str] | None:\n        \"\"\"Generate types for function parameters specified in field.\"\"\"\n        if not param.type:\n            return None\n        heads = [\"type\", param.name]\n        tail = self.render_type(param.type)\n        return heads, tail\n\n    def _exception_formatter(self, exception: Exc) -> tuple[list[str], str]:\n        \"\"\"Derive heads and tail from ``@throws`` blocks.\"\"\"\n        heads = [\"throws\"]\n        if exception.type:\n            heads.append(self.render_type(exception.type, bold=False))\n        tail = render_description(exception.description)\n        return heads, tail\n\n    def _fields(self, obj: TopLevel) -> Iterator[tuple[list[str], str]]:\n        \"\"\"Return an iterable of \"info fields\" to be included in the directive,\n        like params, return values, and exceptions.\n\n        Each field consists of a tuple ``(heads, tail)``, where heads are\n        words that go between colons (as in ``:param string href:``) and\n        tail comes after.\n\n        \"\"\"\n        FIELD_TYPES: list[tuple[str, Callable[[Any], tuple[list[str], str] | None]]] = [\n            (\"type_params\", self._type_param_formatter),\n            (\"params\", self._param_formatter),\n            (\"params\", self._param_type_formatter),\n            (\"properties\", self._param_formatter),\n            (\"properties\", self._param_type_formatter),\n            (\"exceptions\", self._exception_formatter),\n            (\"returns\", self._return_formatter),\n        ]\n        for collection_attr, callback in FIELD_TYPES:\n            for instance in getattr(obj, collection_attr, []):\n                result = callback(instance)\n                if not result:\n                    continue\n                heads, tail = result\n                # If there are line breaks in the tail, the RST parser will\n                # end the field list prematurely.\n                #\n                # TODO: Instead, indent multi-line tails juuuust right, and\n                # we can enjoy block-level constructs within tails:\n                # https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#field-lists.\n                yield [rst.escape(h) for h in heads], unwrapped(tail)\n\n\nclass AutoFunctionRenderer(JsRenderer):\n    _template = \"function.rst\"\n    _renderer_type = \"function\"\n\n    def _template_vars(self, name: str, obj: Function) -> dict[str, Any]:  # type: ignore[override]\n        deprecated = obj.deprecated\n        if not isinstance(deprecated, bool):\n            deprecated = render_description(deprecated)\n        return dict(\n            name=name,\n            type_params=self._type_params(obj),\n            params=self._formal_params(obj),\n            fields=self._fields(obj),\n            description=render_description(obj.description),\n            examples=[render_description(x) for x in obj.examples],\n            deprecated=deprecated,\n            is_optional=obj.is_optional,\n            is_static=obj.is_static,\n            is_async=obj.is_async,\n            see_also=obj.see_alsos,\n            content=\"\\n\".join(self._content),\n        )\n\n\nclass AutoClassRenderer(JsRenderer):\n    _template = \"class.rst\"\n    _renderer_type = \"class\"\n\n    def _template_vars(self, name: str, obj: Class | Interface) -> dict[str, Any]:  # type: ignore[override]\n        # TODO: At the moment, we pull most fields (params, returns,\n        # exceptions, etc.) off the constructor only. We could pull them off\n        # the class itself too in the future.\n        if not isinstance(obj, Class) or not obj.constructor_:\n            # One way or another, it has no constructor, so make a blank one to\n            # keep from repeating this long test for every constructor-using\n            # line in the dict() call:\n            constructor = Function(\n                name=\"\",\n                path=Pathname([]),\n                filename=\"\",\n                deppath=None,\n                description=\"\",\n                line=0,\n                deprecated=False,\n                examples=[],\n                see_alsos=[],\n                properties=[],\n                exported_from=None,\n                is_abstract=False,\n                is_optional=False,\n                is_static=False,\n                is_async=False,\n                is_private=False,\n                type_params=obj.type_params,\n                params=[],\n                exceptions=[],\n                returns=[],\n            )\n        else:\n            constructor = obj.constructor_\n        return dict(\n            name=name,\n            params=self._formal_params(constructor),\n            type_params=self._type_params(obj),\n            fields=self._fields(constructor),\n            examples=[render_description(ex) for ex in constructor.examples],\n            deprecated=constructor.deprecated,\n            see_also=constructor.see_alsos,\n            exported_from=obj.exported_from,\n            class_comment=render_description(obj.description),\n            is_abstract=isinstance(obj, Class) and obj.is_abstract,\n            interfaces=[self.render_type(x) for x in obj.interfaces]\n            if isinstance(obj, Class)\n            else [],\n            is_interface=isinstance(obj, Interface),\n            supers=[self.render_type(x) for x in obj.supers],\n            constructor_comment=render_description(constructor.description),\n            content=\"\\n\".join(self._content),\n            members=self._members_of(\n                obj,\n                include=self._options[\"members\"],\n                exclude=self._options.get(\"exclude-members\", set()),\n                should_include_private=\"private-members\" in self._options,\n            )\n            if \"members\" in self._options\n            else \"\",\n        )\n\n    def _members_of(\n        self,\n        obj: Class | Interface,\n        include: list[str],\n        exclude: list[str],\n        should_include_private: bool,\n    ) -> str:\n        \"\"\"Return RST describing the members of a given class.\n\n        :arg obj Class: The class we're documenting\n        :arg include: List of names of members to include. If empty, include\n            all.\n        :arg exclude: Set of names of members to exclude\n        :arg should_include_private: Whether to include private members\n\n        \"\"\"\n        return \"\\n\\n\".join(\n            self.rst_for(member)\n            for member in members_to_include(\n                obj.members, include, exclude, should_include_private\n            )\n        )\n\n\nclass AutoAttributeRenderer(JsRenderer):\n    _template = \"attribute.rst\"\n    _renderer_type = \"attribute\"\n\n    def _template_vars(self, name: str, obj: Attribute | TypeAlias) -> dict[str, Any]:  # type: ignore[override]\n        is_optional = False\n        ty = self.render_type(obj.type)\n        if isinstance(obj, Attribute):\n            is_optional = obj.is_optional\n            if obj.readonly:\n                ty = \"readonly \" + ty\n        type_params = \"\"\n        is_type_alias = isinstance(obj, TypeAlias)\n        fields: Iterator[tuple[list[str], str]] = iter([])\n        if isinstance(obj, TypeAlias):\n            type_params = self._type_params(obj)\n            fields = self._fields(obj)\n        return dict(\n            name=name,\n            is_type_alias=is_type_alias,\n            type_params=type_params,\n            fields=fields,\n            description=render_description(obj.description),\n            deprecated=obj.deprecated,\n            is_optional=is_optional,\n            see_also=obj.see_alsos,\n            examples=[render_description(ex) for ex in obj.examples],\n            type=ty,\n            content=\"\\n\".join(self._content),\n        )\n\n\n_SECTION_ORDER = [\"type_aliases\", \"attributes\", \"functions\", \"interfaces\", \"classes\"]\n\n\nclass AutoModuleRenderer(JsRenderer):\n    def _parse_path(self, arg: str) -> None:\n        # content, arguments, options, app: all need to be accessible to\n        # template_vars, so we bring them in on construction and stow them away\n        # on the instance so calls to template_vars don't need to concern\n        # themselves with what it needs.\n        self._explicit_formal_params = \"\"\n        self._partial_path = arg.split(\"/\")\n\n    def get_object(self) -> Module:  # type:ignore[override]\n        analyzer: Analyzer = self._app._sphinxjs_analyzer  # type:ignore[attr-defined]\n        assert isinstance(analyzer, TsAnalyzer)\n        return analyzer._modules_by_path.get(self._partial_path)\n\n    def rst_for_group(self, objects: Iterable[TopLevel]) -> list[str]:\n        return [\n            self.rst_for(obj)\n            for obj in objects\n            if \"@omitFromAutoModule\" not in obj.modifier_tags\n        ]\n\n    def rst(  # type:ignore[override]\n        self,\n        partial_path: list[str],\n        obj: Module,\n        use_short_name: bool = False,\n    ) -> str:\n        rst: list[Sequence[str]] = []\n        rst.append([f\".. js:module:: {''.join(partial_path)}\"])\n        for group_name in _SECTION_ORDER:\n            rst.append(self.rst_for_group(getattr(obj, group_name)))\n        return \"\\n\\n\".join([\"\\n\\n\".join(r) for r in rst if r])\n\n\nclass AutoSummaryRenderer(Renderer):\n    def _parse_path(self, arg: str) -> None:\n        # content, arguments, options, app: all need to be accessible to\n        # template_vars, so we bring them in on construction and stow them away\n        # on the instance so calls to template_vars don't need to concern\n        # themselves with what it needs.\n        self._explicit_formal_params = \"\"\n        self._partial_path = arg.split(\"/\")\n\n    def get_object(self) -> Module:\n        analyzer: Analyzer = self._app._sphinxjs_analyzer  # type:ignore[attr-defined]\n        assert isinstance(analyzer, TsAnalyzer)\n        return analyzer._modules_by_path.get(self._partial_path)\n\n    def rst_nodes(self) -> list[Node]:\n        module = self.get_object()\n        pkgname = \"\".join(self._partial_path)\n\n        result: list[Node] = []\n        for group_name in _SECTION_ORDER:\n            group_objects = getattr(module, group_name)\n            if not group_objects:\n                continue\n            n = nodes.container()\n            n += self.format_heading(group_name.replace(\"_\", \" \").title() + \":\")\n            table_items = self.get_summary_table(pkgname, group_objects)\n            n += self.format_table(table_items)\n            n[\"classes\"] += [\"jssummarytable\", group_name]\n            result.append(n)\n        return result\n\n    def format_heading(self, text: str) -> Node:\n        \"\"\"Make a section heading. This corresponds to the rst: \"**Heading:**\"\n        autodocsumm uses headings like that, so this will match that style.\n        \"\"\"\n        heading = nodes.paragraph(\"\")\n        strong = nodes.strong(\"\")\n        strong.append(nodes.Text(text))\n        heading.append(strong)\n        return heading\n\n    def extract_summary(self, descr: str) -> str:\n        \"\"\"Wrapper around autosummary extract_summary that is easier to use.\n        It seems like colons need escaping for some reason.\n        \"\"\"\n        colon_esc = \"esccolon\\\\\\xafhoa:\"\n        # extract_summary seems to have trouble if there are Sphinx\n        # directives in descr\n        descr, _, _ = descr.partition(\"\\n..\")\n        document = self._directive.state.document\n        # In Sphinx 9.x, extract_summary takes document.settings directly\n        # instead of the document object.\n        doc_or_settings: Any\n        if sphinx_version_info >= (9, 0):\n            doc_or_settings = document.settings\n        else:\n            doc_or_settings = document\n        return extract_summary(\n            [descr.replace(\":\", colon_esc)], doc_or_settings\n        ).replace(colon_esc, \":\")\n\n    def get_sig(self, obj: TopLevel) -> str:\n        \"\"\"If the object is a function, get its signature (as figured by JsDoc)\"\"\"\n        if isinstance(obj, ir.Function):\n            return AutoFunctionRenderer(\n                self._directive, self._app, arguments=[\"dummy\"]\n            )._formal_params(obj)\n        else:\n            return \"\"\n\n    def get_summary_row(self, pkgname: str, obj: TopLevel) -> tuple[str, str, str]:\n        \"\"\"Get the summary table row for obj.\n\n        The output is designed to be input to format_table. The link name\n        needs to be set up so that :any:`link_name` makes a link to the\n        actual API docs for this object.\n        \"\"\"\n        display_name = obj.name\n        prefix = \"**async** \" if getattr(obj, \"is_async\", False) else \"\"\n        qualifier = \"any\"\n        link_name = pkgname + \".\" + display_name\n        main = f\"{prefix}:{qualifier}:`{display_name} <{link_name}>`\"\n        if slink := obj.block_tags.get(\"summaryLink\"):\n            main = render_description(slink[0])\n        sig = self.get_sig(obj)\n        summary = self.extract_summary(render_description(obj.description))\n        return (main, sig, summary)\n\n    def get_summary_table(\n        self, pkgname: str, group: Iterable[TopLevel]\n    ) -> list[tuple[str, str, str]]:\n        \"\"\"Get the data for a summary tget_summary_tableable. Return value\n        is set up to be an argument of format_table.\n        \"\"\"\n        return [self.get_summary_row(pkgname, obj) for obj in group]\n\n    # This following method is copied almost verbatim from autosummary\n    # (where it is called get_table).\n    #\n    # We have to change the value of one string: qualifier = 'obj   ==>\n    # qualifier = 'any'\n    # https://github.com/sphinx-doc/sphinx/blob/6.0.x/sphinx/ext/autosummary/__init__.py#L375\n    def format_table(self, items: list[tuple[str, str, str]]) -> list[Node]:\n        \"\"\"Generate a proper list of table nodes for autosummary:: directive.\n\n        *items* is a list produced by :meth:`get_items`.\n        \"\"\"\n        table_spec = addnodes.tabular_col_spec()\n        table_spec[\"spec\"] = r\"\\X{1}{2}\\X{1}{2}\"\n\n        table = autosummary_table(\"\")\n        real_table = nodes.table(\"\", classes=[\"longtable\"])\n        table.append(real_table)\n        group = nodes.tgroup(\"\", cols=2)\n        real_table.append(group)\n        group.append(nodes.colspec(\"\", colwidth=10))\n        group.append(nodes.colspec(\"\", colwidth=90))\n        body = nodes.tbody(\"\")\n        group.append(body)\n\n        def append_row(column_texts: list[tuple[str, str]]) -> None:\n            row = nodes.row(\"\")\n            source, line = self._directive.state_machine.get_source_and_line()\n            assert source\n            assert line\n            for [text, cls] in column_texts:\n                node = nodes.paragraph(\"\")\n                vl = StringList()\n                vl.append(text, \"%s:%d:<autosummary>\" % (source, line))\n                with switch_source_input(self._directive.state, vl):\n                    self._directive.state.nested_parse(vl, 0, node)\n                    try:\n                        if isinstance(node[0], nodes.paragraph):\n                            node = node[0]\n                    except IndexError:\n                        pass\n                    entry = nodes.entry(\"\", node)\n                    entry[\"classes\"].append(cls)\n                    row.append(entry)\n            body.append(row)\n\n        for name, sig, summary in items:\n            # The body of this loop is changed from copied code.\n            if \"nosignatures\" not in self._options:\n                sig = rst.escape(sig)\n                if sig:\n                    sig = f\"**{sig}**\"\n                name = rf\"{name}\\ {sig}\"\n            append_row([(name, \"name\"), (summary, \"summary\")])\n\n        return [table_spec, table]\n"
  },
  {
    "path": "sphinx_js/suffix_tree.py",
    "content": "from collections.abc import Iterable, Sequence\nfrom typing import (\n    Any,\n    Generic,\n    TypedDict,\n    TypeVar,\n)\n\nT = TypeVar(\"T\")\n\n\n# In Python 3.10: Cannot inherit from TypedDict and Generic.\nclass _Tree(TypedDict, total=False):\n    value: Any\n    subtree: dict[str, \"_Tree\"]\n\n\nclass SuffixTree(Generic[T]):\n    \"\"\"A suffix tree in which you can use anything hashable as a path segment\n    and anything at all as a value\n\n    \"\"\"\n\n    def __init__(self) -> None:\n        #: Internal structure is like... ::\n        #:\n        #:     Tree = {value?: Any,\n        #:             subtree?: {segmentFoo: Tree, segmentBar: Tree, ...}}\n        #\n        #: A Tree can have a value key, a subtree key, or both. Subtree dicts\n        #: always have at least 1 key. Every subtree has at least one value,\n        #: directly or indirectly. ``self._tree`` itself is a Tree.\n        self._tree: _Tree = {}\n\n    def add(self, unambiguous_segments: Sequence[str], value: T) -> None:\n        \"\"\"Add an item to the tree.\n\n        :arg unambiguous_segments: A list of path segments that correspond\n            uniquely to the stored value\n        :arg value: Any value you want to fetch by path\n\n        \"\"\"\n        tree = self._tree\n        for seg in reversed(unambiguous_segments):\n            tree = tree.setdefault(\"subtree\", {}).setdefault(seg, {})\n        if \"value\" in tree:\n            raise PathTaken(unambiguous_segments)\n        else:\n            tree[\"value\"] = value\n\n    def add_many(\n        self, segments_and_values: Iterable[tuple[Sequence[str], Any]]\n    ) -> None:\n        \"\"\"Add a batch of items to the tree all at once, and collect any\n        errors.\n\n        :arg segments_and_values: An iterable of (unambiguous segment path,\n            value)\n\n        If any of the segment paths are duplicates, raise PathsTaken.\n\n        \"\"\"\n        conflicts = []\n        for segs, value in segments_and_values:\n            try:\n                self.add(segs, value)\n            except PathTaken as conflict:\n                conflicts.append(conflict.segments)\n        if conflicts:\n            raise PathsTaken(conflicts)\n\n    def get_with_path(self, segments: Sequence[str]) -> tuple[T, Sequence[str]]:\n        \"\"\"Return the value stored at a path ending in the given segments,\n        along with the full path found.\n\n        :arg segments list: A possibly ambiguous suffix of a path\n\n        If no paths are found with the given suffix, raise SuffixNotFound. If\n        multiple are found, raise SuffixAmbiguous. This is true even if\n        the given suffix is a full path to a value: we want to flag the\n        ambiguity to the user, even if it might sometimes be more convenient to\n        assume the intention was to present a full path.\n\n        \"\"\"\n        # Keep walking down subtrees (returning NotFound if failed) until we\n        # run out of segs:\n        tree = self._tree\n        for seg in reversed(segments):\n            try:\n                tree = tree[\"subtree\"][seg]\n            except KeyError:\n                raise SuffixNotFound(segments)\n\n        # If there's only a value there, return it:\n        if \"value\" in tree:\n            if \"subtree\" in tree:\n                raise SuffixAmbiguous(\n                    segments, list(tree[\"subtree\"].keys()), or_ends_here=True\n                )\n            return tree[\"value\"], segments\n\n        # Else if there's a subtree, follow its 1-key subtrees forever, since\n        # there's no ambiguity there:\n        additional_segments = []\n        while len(tree.get(\"subtree\", {})) == 1:\n            only_key = next(iter(tree[\"subtree\"].keys()))\n            tree = tree[\"subtree\"][only_key]\n            additional_segments.append(only_key)\n\n        # If we arrived at a spot with multiple possibilities, yell:\n        if len(tree.get(\"subtree\", {})) > 1:\n            raise SuffixAmbiguous(segments, list(tree[\"subtree\"].keys()))\n\n        # Otherwise, return the found value. There must always be a value here\n        # because add() always eventually adds (or finds) a value after a chain\n        # of subtrees. If there were multiple subtrees from here, we would have\n        # raised SuffixAmbiguous above. If there were a single one, we would\n        # have followed it. So, since subtrees always eventually terminate in a\n        # value, we must be at one now.\n        return tree[\"value\"], (list(reversed(additional_segments)) + list(segments))\n\n    def get(self, segments: Sequence[str]) -> T:\n        return self.get_with_path(segments)[0]\n\n\nclass SuffixError(Exception):\n    def __init__(self, segments: Sequence[str]):\n        self.segments = segments\n\n    _message: str\n\n    def __str__(self) -> str:\n        return self._message % \"\".join(self.segments)\n\n\nclass PathTaken(SuffixError):\n    \"\"\"Attempted to add a suffix that was already in the tree.\"\"\"\n\n    _message = \"Attempted to add a path already in the suffix tree: %s.\"\n\n\nclass PathsTaken(Exception):\n    \"\"\"One or more JS objects had the same paths.\n\n    Rolls up multiple PathTaken exceptions for mass reporting.\n\n    \"\"\"\n\n    def __init__(self, conflicts: list[Sequence[str]]) -> None:\n        \"\"\"\n        :arg conflicts: A list of paths, each given as a list of segments\n        \"\"\"\n        self.conflicts = conflicts\n\n    def __str__(self) -> str:\n        return (\n            \"Your code contains multiple documented objects at each of \"\n            \"these paths:\\n\\n\"\n            + \"\\n  \".join(\"\".join(c) for c in self.conflicts)\n            + \"\\n\\n\"\n            \"We won't know which one you're \"\n            \"talking about.\"\n        )\n\n\nclass SuffixNotFound(SuffixError):\n    \"\"\"No keys ended in the given suffix.\"\"\"\n\n    _message = \"No path found ending in %s.\"\n\n\nclass SuffixAmbiguous(SuffixError):\n    \"\"\"There were multiple keys found ending in the suffix.\"\"\"\n\n    def __init__(\n        self,\n        segments: Sequence[str],\n        next_possible_keys: list[str],\n        or_ends_here: bool = False,\n    ) -> None:\n        super().__init__(segments)\n        self.next_possible_keys = next_possible_keys\n        self.or_ends_here = or_ends_here\n\n    def __str__(self) -> str:\n        ends_here_msg = (\n            \" Or it could end without any of them.\" if self.or_ends_here else \"\"\n        )\n        return \"Ambiguous path: {} could continue as any of {}.{}\".format(\n            \"\".join(self.segments),\n            self.next_possible_keys,\n            ends_here_msg,\n        )\n"
  },
  {
    "path": "sphinx_js/templates/attribute.rst",
    "content": "{% import 'common.rst' as common %}\n\n{% if is_type_alias -%}\n.. js:typealias:: {{ name }}{{ type_params }}\n{%- else -%}\n.. js:attribute:: {{ name }}{{ '?' if is_optional else '' }}\n{%- endif %}\n\n\n   {{ common.deprecated(deprecated)|indent(3) }}\n\n   {% if type -%}\n      .. rst-class:: js attribute type\n\n          type: {{ type|indent(3) }}\n   {%- endif %}\n\n   {% if description -%}\n     {{ description|indent(3) }}\n   {%- endif %}\n\n   {% if is_type_alias -%}\n     {{ common.fields(fields) | indent(3) }}\n   {%- endif %}\n\n   {{ common.examples(examples)|indent(3) }}\n\n   {{ content|indent(3) }}\n\n   {{ common.see_also(see_also)|indent(3) }}\n"
  },
  {
    "path": "sphinx_js/templates/class.rst",
    "content": "{% import 'common.rst' as common %}\n\n{% if is_interface -%}\n.. js:interface:: {{ name }}{{ type_params }}{{ params }}\n{%- else -%}\n.. js:class:: {{ name }}{{ type_params }}{{ params }}\n{%- endif %}\n\n   {{ common.deprecated(deprecated)|indent(3) }}\n\n   {% if class_comment -%}\n     {{ class_comment|indent(3) }}\n   {%- endif %}\n\n   {% if is_abstract -%}\n     *abstract*\n   {%- endif %}\n\n   {{ common.exported_from(exported_from)|indent(3) }}\n\n   {% if supers -%}\n     **Extends:**\n       {% for super in supers -%}\n         - {{ super }}\n       {% endfor %}\n   {%- endif %}\n\n   {% if interfaces -%}\n     **Implements:**\n       {% for interface in interfaces -%}\n         - {{ interface }}\n       {% endfor %}\n   {%- endif %}\n\n   {% if constructor_comment -%}\n     {{ constructor_comment|indent(3) }}\n   {%- endif %}\n\n   {{ common.fields(fields) | indent(3) }}\n\n   {{ common.examples(examples)|indent(3) }}\n\n   {{ content|indent(3) }}\n\n   {% if members -%}\n     {{ members|indent(3) }}\n   {%- endif %}\n\n   {{ common.see_also(see_also)|indent(3) }}\n"
  },
  {
    "path": "sphinx_js/templates/common.rst",
    "content": "{% macro deprecated(message) %}\n{% if message -%}\n.. note::\n\n   Deprecated\n   {%- if message is string -%}: {{ message }}{% else %}.{% endif -%}\n{%- endif %}\n{% endmacro %}\n\n{% macro examples(items) %}\n{% for example in items %}\n\n.. admonition:: Example\n\n   {{ example|indent(3) }}\n{% endfor %}\n{% endmacro %}\n\n{% macro see_also(items) %}\n{% if items -%}\n.. seealso::\n\n   {% for reference in items -%}\n   - :any:`{{ reference }}`\n   {% endfor %}\n{%- endif %}\n{% endmacro %}\n\n{% macro exported_from(pathname) %}\n{% if pathname -%}\n    *exported from* :js:mod:`{{ pathname.dotted() }}`\n{%- endif %}\n{% endmacro %}\n\n{% macro fields(items) %}\n{% for heads, tail in items -%}\n   :{{ heads|join(' ') }}: {{ tail }}\n{% endfor %}\n{% endmacro %}\n"
  },
  {
    "path": "sphinx_js/templates/function.rst",
    "content": "{% import 'common.rst' as common %}\n\n.. js:function:: {{ name }}{{ '?' if is_optional else '' }}{{ type_params }}{{ params }}\n   {% if is_static -%}\n   :static:\n   {% endif %}\n   {%- if is_async -%}\n   :async:\n   {% endif %}\n\n   {{ common.deprecated(deprecated)|indent(3) }}\n\n   {% if description -%}\n     {{ description|indent(3) }}\n   {%- endif %}\n\n   {{ common.fields(fields) | indent(3) }}\n\n   {{ common.examples(examples)|indent(3) }}\n\n   {{ content|indent(3) }}\n\n   {{ common.see_also(see_also)|indent(3) }}\n"
  },
  {
    "path": "sphinx_js/typedoc.py",
    "content": "\"\"\"Converter from TypeDoc output to IR format\"\"\"\n\nimport os\nimport pathlib\nimport re\nimport subprocess\nfrom collections.abc import Iterable, Iterator, Sequence\nfrom errno import ENOENT\nfrom functools import cache\nfrom json import load\nfrom operator import attrgetter\nfrom pathlib import Path\nfrom tempfile import NamedTemporaryFile\nfrom typing import Any, Literal\n\nfrom sphinx.application import Sphinx\nfrom sphinx.errors import SphinxError\n\nfrom . import ir\nfrom .analyzer_utils import Command, search_node_modules\nfrom .suffix_tree import SuffixTree\n\n__all__ = [\"Analyzer\"]\n\nMIN_TYPEDOC_VERSION = (0, 25, 0)\n\n\n@cache\ndef typedoc_version_info(typedoc: str) -> tuple[tuple[int, ...], tuple[int, ...]]:\n    result = subprocess.run(\n        [typedoc, \"--version\"],\n        capture_output=True,\n        encoding=\"utf8\",\n        check=True,\n    )\n    lines = result.stdout.strip().splitlines()\n    m = re.search(r\"TypeDoc ([0-9]+\\.[0-9]+\\.[0-9]+)\", lines[0])\n    assert m\n    typedoc_version = tuple(int(x) for x in m.group(1).split(\".\"))\n    m = re.search(r\"TypeScript ([0-9]+\\.[0-9]+\\.[0-9]+)\", lines[1])\n    assert m\n    typescript_version = tuple(int(x) for x in m.group(1).split(\".\"))\n    return typedoc_version, typescript_version\n\n\ndef version_to_str(t: Sequence[int]) -> str:\n    return \".\".join(str(x) for x in t)\n\n\ndef typedoc_output(\n    abs_source_paths: Sequence[str],\n    base_dir: str,\n    sphinx_conf_dir: str | pathlib.Path,\n    typedoc_config_path: str | None,\n    tsconfig_path: str | None,\n    ts_sphinx_js_config: str | None,\n) -> tuple[list[ir.TopLevelUnion], dict[str, Any]]:\n    \"\"\"Return the loaded JSON output of the TypeDoc command run over the given\n    paths.\"\"\"\n    typedoc = search_node_modules(\"typedoc\", \"typedoc/bin/typedoc\", sphinx_conf_dir)\n    typedoc_version, _ = typedoc_version_info(typedoc)\n    if typedoc_version < MIN_TYPEDOC_VERSION:\n        raise RuntimeError(\n            f\"Typedoc version {version_to_str(typedoc_version)} is too old, minimum required is {version_to_str(MIN_TYPEDOC_VERSION)}\"\n        )\n\n    env = os.environ.copy()\n    env[\"TYPEDOC_NODE_MODULES\"] = str(Path(typedoc).parents[3].resolve())\n    command = Command(\"npx\")\n    command.add(\"tsx@4.15.8\")\n    dir = Path(__file__).parent.resolve() / \"js\"\n    command.add(\"--tsconfig\", str(dir / \"tsconfig.json\"))\n    command.add(\"--import\", str(dir / \"registerImportHook.mjs\"))\n    command.add(str(dir / \"main.ts\"))\n    if ts_sphinx_js_config:\n        command.add(\"--sphinxJsConfig\", ts_sphinx_js_config)\n    command.add(\"--entryPointStrategy\", \"expand\")\n\n    if typedoc_config_path:\n        typedoc_config_path = str(\n            (Path(sphinx_conf_dir) / typedoc_config_path).absolute()\n        )\n        command.add(\"--options\", typedoc_config_path)\n\n    if tsconfig_path:\n        tsconfig_path = str((Path(sphinx_conf_dir) / tsconfig_path).absolute())\n        command.add(\"--tsconfig\", tsconfig_path)\n\n    command.add(\"--basePath\", base_dir)\n    command.add(\"--excludePrivate\", \"false\")\n\n    with NamedTemporaryFile(mode=\"w+b\", delete=False) as temp:\n        command.add(\"--json\", temp.name, *abs_source_paths)\n        try:\n            subprocess.run(command.make(), check=True, env=env)\n        except OSError as exc:\n            if exc.errno == ENOENT:\n                raise SphinxError(\n                    '%s was not found. Install it using \"npm install -g typedoc\".'\n                    % command.program\n                )\n            else:\n                raise\n        # typedoc emits a valid JSON file even if it finds no TS files in the dir:\n        json_ir, extra_data = load(temp)\n        return ir.json_to_ir(json_ir), extra_data\n\n\nclass Analyzer:\n    _objects_by_path: SuffixTree[ir.TopLevel]\n    _modules_by_path: SuffixTree[ir.Module]\n    _extra_data: dict[str, Any]\n\n    def __init__(\n        self, objects: Sequence[ir.TopLevel], extra_data: dict[str, Any], base_dir: str\n    ) -> None:\n        self._extra_data = extra_data\n        self._base_dir = base_dir\n        self._objects_by_path = SuffixTree()\n        self._objects_by_path.add_many((obj.path.segments, obj) for obj in objects)\n        modules = self._create_modules(objects)\n        self._modules_by_path = SuffixTree()\n        self._modules_by_path.add_many((obj.path.segments, obj) for obj in modules)\n\n    def get_object(\n        self,\n        path_suffix: Sequence[str],\n        as_type: Literal[\"function\", \"class\", \"attribute\"] = \"function\",\n    ) -> ir.TopLevel:\n        \"\"\"Return the IR object with the given path suffix.\n\n        :arg as_type: Ignored\n        \"\"\"\n        return self._objects_by_path.get(path_suffix)\n\n    @classmethod\n    def from_disk(\n        cls, abs_source_paths: Sequence[str], app: Sphinx, base_dir: str\n    ) -> \"Analyzer\":\n        json, extra_data = typedoc_output(\n            abs_source_paths,\n            base_dir=base_dir,\n            sphinx_conf_dir=app.confdir,\n            typedoc_config_path=app.config.jsdoc_config_path,\n            tsconfig_path=app.config.jsdoc_tsconfig_path,\n            ts_sphinx_js_config=app.config.ts_sphinx_js_config,\n        )\n        return cls(json, extra_data, base_dir)\n\n    def _get_toplevel_objects(\n        self, ir_objects: Sequence[ir.TopLevel]\n    ) -> Iterator[tuple[ir.TopLevel, str, str]]:\n        for obj in ir_objects:\n            if not obj.documentation_root:\n                continue\n            assert obj.deppath\n            yield (obj, obj.deppath, obj.kind)\n\n    def _create_modules(self, ir_objects: Sequence[ir.TopLevel]) -> Iterable[ir.Module]:\n        \"\"\"Search through the doclets generated by JsDoc and categorize them by\n        summary section. Skip docs labeled as \"@private\".\n        \"\"\"\n        modules = {}\n        singular_kind_to_plural_kind = {\n            \"class\": \"classes\",\n            \"interface\": \"interfaces\",\n            \"function\": \"functions\",\n            \"attribute\": \"attributes\",\n            \"typeAlias\": \"type_aliases\",\n        }\n        for obj, path, kind in self._get_toplevel_objects(ir_objects):\n            pathparts = path.split(\"/\")\n            for i in range(len(pathparts) - 1):\n                pathparts[i] += \"/\"\n            if path not in modules:\n                modules[path] = ir.Module(\n                    filename=path, deppath=path, path=ir.Pathname(pathparts), line=1\n                )\n            mod = modules[path]\n            getattr(mod, singular_kind_to_plural_kind[kind]).append(obj)\n\n        for mod in modules.values():\n            mod.attributes = sorted(mod.attributes, key=attrgetter(\"name\"))\n            mod.functions = sorted(mod.functions, key=attrgetter(\"name\"))\n            mod.classes = sorted(mod.classes, key=attrgetter(\"name\"))\n            mod.interfaces = sorted(mod.interfaces, key=attrgetter(\"name\"))\n            mod.type_aliases = sorted(mod.type_aliases, key=attrgetter(\"name\"))\n        return modules.values()\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "from pytest import register_assert_rewrite\n\nregister_assert_rewrite(\"tests.testing\")\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "import os\nimport sys\nfrom pathlib import Path\n\nimport sphinx\n\nSPHINX_VERSION = tuple(int(x) for x in sphinx.__version__.split(\".\"))\n\nsys.path.append(str(Path(__file__).parent))\n\nimport pytest\n\nif \"SPHINX_JS_NODE_MODULES\" not in os.environ:\n    for p in [Path(sys.prefix), Path(__file__).parents[1]]:\n        p = p / \"node_modules\"\n        if p.exists():\n            os.environ[\"SPHINX_JS_NODE_MODULES\"] = str(p)\n            break\n    else:\n        raise RuntimeError(\"Couldn't find node_modules\")\n\nfrom sphinx_js.analyzer_utils import search_node_modules\nfrom sphinx_js.typedoc import typedoc_version_info\n\nTYPEDOC = search_node_modules(\"typedoc\", \"typedoc/bin/typedoc\", \"\")\nTYPEDOC_VERSION = typedoc_version_info(TYPEDOC)[0]\n\n\npytest_plugins = \"sphinx.testing.fixtures\"\n\n# Exclude 'roots' dirs for pytest test collector\ncollect_ignore = [\"roots\"]\n\n\n@pytest.fixture(scope=\"session\")\ndef rootdir():\n    rootdir = Path(__file__).parent.resolve() / \"roots\"\n    if SPHINX_VERSION < (7, 0, 0):\n        from sphinx.testing.path import path\n\n        return path(rootdir)\n    return rootdir\n"
  },
  {
    "path": "tests/roots/test-incremental_js/a.js",
    "content": "/**\n * Class doc.\n */\nclass ClassA {\n  /**\n   * Static.\n   */\n  static noUseOfThis() {}\n\n  /**\n   * Here.\n   */\n  methodA() {}\n}\n"
  },
  {
    "path": "tests/roots/test-incremental_js/a.rst",
    "content": "module a\n========\n\n.. js:autoclass:: ClassA\n"
  },
  {
    "path": "tests/roots/test-incremental_js/a_b.rst",
    "content": "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",
    "content": "module b\n========\n\n.. js:autoclass:: ClassB\n"
  },
  {
    "path": "tests/roots/test-incremental_js/conf.py",
    "content": "extensions = [\"sphinx_js\"]\n\n# Minimal stuff needed for Sphinx to work:\nsource_suffix = \".rst\"\nmaster_doc = \"index\"\nauthor = \"Nick Alexander\"\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\njs_source_path = [\".\", \"inner\"]\nroot_for_relative_js_paths = \".\"\n# Temp directories on macOS have internal directories starting with\n# \"_\", running afoul of https://github.com/jsdoc/jsdoc/issues/1328.\njsdoc_config_path = \"jsdoc.json\"\n"
  },
  {
    "path": "tests/roots/test-incremental_js/index.rst",
    "content": "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",
    "content": "/**\n * Class doc.\n */\nclass ClassB {\n  /**\n   * Static.\n   */\n  static noUseOfThis() {}\n\n  /**\n   * Here.\n   */\n  methodB() {}\n}\n"
  },
  {
    "path": "tests/roots/test-incremental_js/jsdoc.json",
    "content": "{\n  \"source\": {\n    \"includePattern\": \".+\\\\.js(doc|x)?$\"\n  }\n}\n"
  },
  {
    "path": "tests/roots/test-incremental_js/unrelated.rst",
    "content": "unrelated\n=========\n"
  },
  {
    "path": "tests/roots/test-incremental_ts/a.rst",
    "content": "module a\n========\n\n.. js:autoclass:: ClassA\n"
  },
  {
    "path": "tests/roots/test-incremental_ts/a.ts",
    "content": "export class ClassA {\n  constructor() {}\n\n  /**\n   * Here.\n   */\n  methodA() {}\n}\n"
  },
  {
    "path": "tests/roots/test-incremental_ts/a_b.rst",
    "content": "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",
    "content": "module b\n========\n\n.. js:autoclass:: ClassB\n"
  },
  {
    "path": "tests/roots/test-incremental_ts/conf.py",
    "content": "extensions = [\"sphinx_js\"]\n\n# Minimal stuff needed for Sphinx to work:\nsource_suffix = \".rst\"\nmaster_doc = \"index\"\nauthor = \"Nick Alexander\"\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\njs_language = \"typescript\"\njs_source_path = [\".\", \"inner\"]\nroot_for_relative_js_paths = \".\"\n# Temp directories on macOS have internal directories starting with\n# \"_\", running afoul of https://github.com/jsdoc/jsdoc/issues/1328.\njsdoc_config_path = \"tsconfig.json\"\n"
  },
  {
    "path": "tests/roots/test-incremental_ts/index.rst",
    "content": "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",
    "content": "export class ClassB {\n  constructor() {}\n\n  /**\n   * Here.\n   */\n  methodB() {}\n}\n"
  },
  {
    "path": "tests/roots/test-incremental_ts/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es6\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\"\n  }\n}\n"
  },
  {
    "path": "tests/roots/test-incremental_ts/unrelated.rst",
    "content": "unrelated\n=========\n"
  },
  {
    "path": "tests/sphinxJsConfig.ts",
    "content": "export const config = {\n  shouldDestructureArg: (param) => param.name === \"destructureThisPlease\",\n};\n"
  },
  {
    "path": "tests/test.ts",
    "content": "import assert from \"node:assert\";\nimport { test, suite, before } from \"node:test\";\nimport { run } from \"../sphinx_js/js/cli\";\nimport { TopLevelIR, Type } from \"../sphinx_js/js/ir\";\nimport { Application } from \"typedoc\";\n\nfunction joinType(t: Type): string {\n  return t.map((x) => (typeof x === \"string\" ? x : x.name)).join(\"\");\n}\n\nfunction resolveFile(path: string): string {\n  return import.meta.dirname + \"/\" + path;\n}\n\nsuite(\"types.ts\", async () => {\n  let app: Application;\n  let results: TopLevelIR[];\n  let map: Map<string, TopLevelIR>;\n  before(async () => {\n    [app, results] = await run([\n      \"--sphinxJsConfig\",\n      resolveFile(\"sphinxJsConfig.ts\"),\n      \"--entryPointStrategy\",\n      \"expand\",\n      \"--tsconfig\",\n      resolveFile(\"test_typedoc_analysis/source/tsconfig.json\"),\n      \"--basePath\",\n      resolveFile(\"test_typedoc_analysis/source\"),\n      resolveFile(\"test_typedoc_analysis/source/types.ts\"),\n    ]);\n    map = new Map(results.map((res) => [res.name, res]));\n  });\n  function getObject(name: string): TopLevelIR {\n    const obj = map.get(name);\n    assert(obj);\n    return obj;\n  }\n  suite(\"basic\", async () => {\n    for (const [obj_name, type_name] of [\n      [\"bool\", \"boolean\"],\n      [\"num\", \"number\"],\n      [\"str\", \"string\"],\n      [\"array\", \"number[]\"],\n      [\"genericArray\", \"number[]\"],\n      [\"tuple\", \"[string, number]\"],\n      [\"color\", \"Color\"],\n      [\"unk\", \"unknown\"],\n      [\"whatever\", \"any\"],\n      [\"voidy\", \"void\"],\n      [\"undef\", \"undefined\"],\n      [\"nully\", \"null\"],\n      [\"nev\", \"never\"],\n      [\"obj\", \"object\"],\n      [\"sym\", \"symbol\"],\n    ]) {\n      await test(obj_name, () => {\n        const obj = getObject(obj_name);\n        assert.strictEqual(obj.kind, \"attribute\");\n        assert.strictEqual(joinType(obj.type), type_name);\n      });\n    }\n  });\n  await test(\"named_interface\", () => {\n    const obj = getObject(\"interfacer\");\n    assert.strictEqual(obj.kind, \"function\");\n    assert.deepStrictEqual(obj.params[0].type, [\n      {\n        name: \"Interface\",\n        path: [\"./\", \"types.\", \"Interface\"],\n        type: \"internal\",\n      },\n    ]);\n  });\n  await test(\"interface_readonly_member\", () => {\n    const obj = getObject(\"Interface\");\n    assert.strictEqual(obj.kind, \"interface\");\n    const readOnlyNum = obj.members[0];\n    assert.strictEqual(readOnlyNum.kind, \"attribute\");\n    assert.strictEqual(readOnlyNum.name, \"readOnlyNum\");\n    assert.deepStrictEqual(readOnlyNum.type, [\n      { name: \"number\", type: \"intrinsic\" },\n    ]);\n  });\n  await test(\"array\", () => {\n    const obj = getObject(\"overload\");\n    assert.strictEqual(obj.kind, \"function\");\n    assert.deepStrictEqual(obj.params[0].type, [\n      { name: \"string\", type: \"intrinsic\" },\n      \"[]\",\n    ]);\n  });\n  await test(\"literal_types\", () => {\n    const obj = getObject(\"certainNumbers\");\n    assert.strictEqual(obj.kind, \"attribute\");\n    assert.deepStrictEqual(obj.type, [\n      {\n        name: \"CertainNumbers\",\n        path: [\"./\", \"types.\", \"CertainNumbers\"],\n        type: \"internal\",\n      },\n    ]);\n  });\n  await test(\"private_type_alias_1\", () => {\n    const obj = getObject(\"typeIsPrivateTypeAlias1\");\n    assert.strictEqual(obj.kind, \"attribute\");\n    assert.deepStrictEqual(joinType(obj.type), \"{ a: number; b: string; }\");\n  });\n  await test(\"private_type_alias_2\", () => {\n    const obj = getObject(\"typeIsPrivateTypeAlias2\");\n    assert.strictEqual(obj.kind, \"attribute\");\n    assert.deepStrictEqual(joinType(obj.type), \"{ a: number; b: string; }\");\n  });\n});\n"
  },
  {
    "path": "tests/test_build_js/source/code.js",
    "content": "/**\n * Return the ratio of the inline text length of the links in an element to\n * the inline text length of the entire element.\n *\n * @param {Node} node - Something of a single type\n * @throws {PartyError|FartyError} Something with multiple types and a line\n *    that wraps\n * @returns {Number} What a thing\n */\nfunction linkDensity(node) {\n  const length = node.flavors.get(\"paragraphish\").inlineLength;\n  const lengthWithoutLinks = inlineTextLength(\n    node.element,\n    (element) => element.tagName !== \"A\",\n  );\n  return (length - lengthWithoutLinks) / length;\n}\n\n/**\n * Class doc.\n */\nclass ContainingClass {\n  /**\n   * Constructor doc.\n   *\n   * @arg ho A thing\n   */\n  constructor(ho) {\n    /**\n     * A var\n     */\n    this.someVar = 4;\n  }\n\n  /**\n   * Here.\n   * @protected\n   */\n  someMethod(hi) {}\n\n  /**\n   * Setting this also frobs the frobnicator.\n   */\n  get bar() {\n    return this._bar;\n  }\n  set bar(baz) {\n    this._bar = _bar;\n  }\n\n  /**\n   * Another.\n   */\n  anotherMethod() {}\n\n  /**\n   * More.\n   */\n  yetAnotherMethod() {}\n\n  /**\n   * Private thing.\n   * @private\n   */\n  secret() {}\n}\n\n// We won't add any new members to this class, because it would break some tests.\n/** Closed class. */\nclass ClosedClass {\n  /**\n   * Public thing.\n   */\n  publical() {}\n\n  /**\n   * Public thing 2.\n   */\n  publical2() {}\n\n  /**\n   * Public thing 3.\n   */\n  publical3() {}\n}\n\n/** Non-alphabetical class. */\nclass NonAlphabetical {\n  /** Fun z. */\n  z() {}\n\n  /** Fun a. */\n  a() {}\n}\n\n/**\n * This doesn't emit a paramnames key in meta.code.\n * @class\n */\nconst NoParamnames = {};\n\n/** Thing to be shadowed in more_code.js */\nfunction shadow() {}\n\n/**\n * @typedef {Object} TypeDefinition\n * @property {Number} width - width in pixels\n */\n\n/**\n * Some global callback\n * @callback requestCallback\n * @param {number} responseCode\n */\n\n/**\n * JSDoc example tag\n *\n * @example\n * // This is the example.\n * exampleTag();\n */\nfunction exampleTag() {}\n\n/**\n * JSDoc example tag for class\n *\n * @example\n * // This is the example.\n * new ExampleClass();\n */\nclass ExampleClass {}\n\n/**\n * JSDoc example tag for attribute\n *\n * @example\n * // This is the example.\n * console.log(ExampleAttribute);\n */\nconst ExampleAttribute = null;\n\n/**\n * @param {number} p1\n * @param {Object} p2\n * @param {string} p2.foo\n * @param {string} p2.bar\n */\nfunction destructuredParams(p1, { foo, bar }) {}\n\n/**\n * @param a_ Snorf\n * @param {type_} b Borf_\n * @returns {rtype_} Dorf_\n */\nfunction injection() {}\n\n/**\n * @param {function} [func=() => 5]\n * @param [str=a string with \" quote]\n * @param {string} [strNum=42]\n * @param {string} [strBool=true]\n * @param [num=5]\n * @param [nil=null]\n */\nfunction defaultsDocumentedInDoclet(func, str, strNum, strBool, num, nil) {}\n\n/**\n * @param [num]\n * @param [str]\n * @param [bool]\n * @param [nil]\n */\nfunction defaultsDocumentedInCode(\n  num = 5,\n  str = \"true\",\n  bool = true,\n  nil = null,\n) {}\n\n/**\n * Variadic parameter\n * @param a\n * @param args\n */\nfunction variadicParameter(a, ...args) {}\n\n/** @deprecated */\nfunction deprecatedFunction() {}\n/** @deprecated don't use anymore */\nfunction deprecatedExplanatoryFunction() {}\n\n/** @deprecated */\nconst DeprecatedAttribute = null;\n/** @deprecated don't use anymore */\nconst DeprecatedExplanatoryAttribute = null;\n\n/** @deprecated */\nclass DeprecatedClass {}\n/** @deprecated don't use anymore */\nclass DeprecatedExplanatoryClass {}\n\n/**\n * @see DeprecatedClass\n * @see deprecatedFunction\n * @see DeprecatedAttribute\n */\nfunction seeFunction() {}\n/**\n * @see DeprecatedClass\n * @see deprecatedFunction\n * @see DeprecatedAttribute\n */\nconst SeeAttribute = null;\n/**\n * @see DeprecatedClass\n * @see deprecatedFunction\n * @see DeprecatedAttribute\n */\nclass SeeClass {}\n\n/**\n * @arg fnodeA {Node|Fnode}\n */\nfunction union(fnodeA) {}\n\n/**\n * Once upon a time, there was a large bear named Sid. Sid wore green pants\n * with blue stripes and pink polka dots.\n *\n * * List!\n *\n * @param a A is the first letter of the Roman alphabet. It is used in such\n *     illustrious words as aardvark and artichoke.\n * @param b Next param, which should be part of the same field list\n */\nfunction longDescriptions(a, b) {}\n\n/**\n * Class doc.\n */\nclass SimpleClass {\n  /**\n   * Static.\n   *\n   * @see nonStaticMethod\n   */\n  static staticMethod() {}\n\n  /**\n   * Non-static member.\n   *\n   * @see staticMethod\n   */\n  nonStaticMethod() {}\n}\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoattribute.rst",
    "content": ".. js:autoattribute:: ContainingClass#someVar\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoattribute_deprecated.rst",
    "content": ".. js:autoattribute:: DeprecatedAttribute\n\n.. js:autoattribute:: DeprecatedExplanatoryAttribute\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoattribute_example.rst",
    "content": ".. js:autoattribute:: ExampleAttribute\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoattribute_see.rst",
    "content": ".. js:autoattribute:: SeeAttribute\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoclass.rst",
    "content": ".. js:autoclass:: ContainingClass\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoclass_alphabetical.rst",
    "content": ".. js:autoclass:: NonAlphabetical\n   :members:\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoclass_deprecated.rst",
    "content": ".. js:autoclass:: DeprecatedClass\n\n.. js:autoclass:: DeprecatedExplanatoryClass\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoclass_example.rst",
    "content": ".. js:autoclass:: ExampleClass\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoclass_exclude_members.rst",
    "content": ".. js:autoclass:: ClosedClass\n   :members:\n   :exclude-members: publical2, publical3\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoclass_members.rst",
    "content": ".. js:autoclass:: ContainingClass\n   :members:\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoclass_members_list.rst",
    "content": ".. js:autoclass:: ClosedClass\n   :members: publical3, publical\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoclass_members_list_star.rst",
    "content": ".. js:autoclass:: ContainingClass\n   :members: bar, *, someMethod\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoclass_no_paramnames.rst",
    "content": "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",
    "content": ".. js:autoclass:: ContainingClass\n   :members:\n   :private-members:\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autoclass_see.rst",
    "content": ".. js:autoclass:: SeeClass\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_callback.rst",
    "content": ".. js:autofunction:: requestCallback\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_defaults_code.rst",
    "content": ".. js:autofunction:: defaultsDocumentedInCode\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_defaults_doclet.rst",
    "content": ".. js:autofunction:: defaultsDocumentedInDoclet\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_deprecated.rst",
    "content": ".. js:autofunction:: deprecatedFunction\n\n.. js:autofunction:: deprecatedExplanatoryFunction\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_destructured_params.rst",
    "content": ".. js:autofunction:: destructuredParams\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_example.rst",
    "content": ".. js:autofunction:: exampleTag\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_explicit.rst",
    "content": ".. js:autofunction:: linkDensity(snorko, borko[, forko])\n\n   Things are ``neat``.\n\n   Off the beat.\n\n   * Sweet\n   * Fleet\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_long.rst",
    "content": ".. js:autofunction:: ContainingClass#someMethod\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_minimal.rst",
    "content": ".. js:autofunction:: linkDensity\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_see.rst",
    "content": ".. js:autofunction:: seeFunction\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_short.rst",
    "content": ".. js:autofunction:: ContainingClass#someMethod\n   :short-name:\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_static.rst",
    "content": ".. js:autoclass:: SimpleClass\n    :members:\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_typedef.rst",
    "content": ".. js:autofunction:: TypeDefinition\n"
  },
  {
    "path": "tests/test_build_js/source/docs/autofunction_variadic.rst",
    "content": ".. js:autofunction:: variadicParameter\n"
  },
  {
    "path": "tests/test_build_js/source/docs/avoid_shadowing.rst",
    "content": ".. js:autofunction:: more_code.shadow\n"
  },
  {
    "path": "tests/test_build_js/source/docs/conf.py",
    "content": "extensions = [\"sphinx_js\"]\n\n# Minimal stuff needed for Sphinx to work:\nsource_suffix = \".rst\"\nmaster_doc = \"index\"\nauthor = \"Erik Rose\"\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\n"
  },
  {
    "path": "tests/test_build_js/source/docs/getter_setter.rst",
    "content": ".. js:autoattribute:: ContainingClass#bar\n"
  },
  {
    "path": "tests/test_build_js/source/docs/index.rst",
    "content": ""
  },
  {
    "path": "tests/test_build_js/source/docs/injection.rst",
    "content": ".. js:autofunction:: injection\n"
  },
  {
    "path": "tests/test_build_js/source/docs/union.rst",
    "content": ".. js:autofunction:: union\n"
  },
  {
    "path": "tests/test_build_js/source/docs/unwrapped.rst",
    "content": ".. js:autofunction:: longDescriptions\n"
  },
  {
    "path": "tests/test_build_js/source/more_code.js",
    "content": "// This file is processed after code.js and so tends to shadow things.\n\n/** Another thing named shadow, to threaten to shadow the one in code.js */\nfunction shadow() {}\n"
  },
  {
    "path": "tests/test_build_js/test_build_js.py",
    "content": "from sphinx import __version__ as sphinx_version\n\nfrom tests.testing import SphinxBuildTestCase\n\nSPHINX_VERSION = tuple(int(part) for part in sphinx_version.split(\".\"))\n\n# NOTE(willkg): This is the version of Sphinx that removes trailing \" --\" from\n# :params: lines when there is no description.\nSPHINX_7_3_0 = (7, 3, 0)\n\n\nclass Tests(SphinxBuildTestCase):\n    \"\"\"Tests which require our big JS Sphinx tree to be built.\n\n    Yes, it's too coupled.\n\n    Many of these are renderer tests, but some indirectly test JS analysis.\n    These latter are left over from when JS was the only supported language and\n    had its assumptions coded into the renderers.\n\n    \"\"\"\n\n    def test_autofunction_minimal(self):\n        \"\"\"Make sure we render correctly and pull the params out of the JS code\n        when only the function name is provided.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_minimal\", \"linkDensity(node)\" + DESCRIPTION + FIELDS\n        )\n\n    def test_autofunction_explicit(self):\n        \"\"\"Make sure any explicitly provided params override the ones from the\n        code, and make sure any explicit arbitrary RST content gets\n        preserved.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_explicit\",\n            \"linkDensity(snorko, borko[, forko])\" + DESCRIPTION + FIELDS + CONTENT,\n        )\n\n    def test_autofunction_short(self):\n        \"\"\"Make sure the ``:short-name:`` option works.\"\"\"\n        self._file_contents_eq(\"autofunction_short\", \"someMethod(hi)\\n\\n   Here.\\n\")\n\n    def test_autofunction_long(self):\n        \"\"\"Make sure instance methods get converted to dotted notation which\n        indexes better in Sphinx.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_long\", \"ContainingClass.someMethod(hi)\\n\\n   Here.\\n\"\n        )\n\n    def test_autofunction_typedef(self):\n        \"\"\"Make sure @typedef uses can be documented with autofunction.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_typedef\",\n            \"TypeDefinition()\\n\\n   Arguments:\\n      * **width** (**Number**) -- width in pixels\\n\",\n        )\n\n    def test_autofunction_callback(self):\n        \"\"\"Make sure @callback uses can be documented with autofunction.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_callback\",\n            \"requestCallback(responseCode)\\n\\n   Some global callback\\n\\n   Arguments:\\n      * **responseCode** (**number**)\\n\",\n        )\n\n    def test_autofunction_example(self):\n        \"\"\"Make sure @example tags can be documented with autofunction.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_example\",\n            \"exampleTag()\\n\\n\"\n            \"   JSDoc example tag\\n\\n\"\n            \"   Example:\\n\\n\"\n            \"      // This is the example.\\n\"\n            \"      exampleTag();\\n\",\n        )\n\n    def test_autofunction_destructured_params(self):\n        \"\"\"Make sure that all documented params appears in the function\n        definition.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_destructured_params\",\n            \"destructuredParams(p1, p2)\\n\\n\"\n            \"   Arguments:\\n\"\n            \"      * **p1** (**number**)\\n\\n\"\n            \"      * **p2** (**Object**)\\n\\n\"\n            \"      * **p2.foo** (**string**)\\n\\n\"\n            \"      * **p2.bar** (**string**)\\n\",\n        )\n\n    def test_autofunction_defaults_in_doclet(self):\n        \"\"\"Make sure param default values appear in the function definition,\n        when defined in JSDoc.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_defaults_doclet\",\n            'defaultsDocumentedInDoclet(func=() => 5, str=\"a string with \\\\\" quote\", strNum=\"42\", strBool=\"true\", num=5, nil=null)\\n\\n'\n            \"   Arguments:\\n\"\n            \"      * **func** (**function**)\\n\\n\"\n            \"      * **strNum** (**string**)\\n\\n\"\n            \"      * **strBool** (**string**)\\n\",\n        )\n\n    def test_autofunction_defaults_in_code(self):\n        \"\"\"Make sure param default values appear in the function definition,\n        when defined in code.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_defaults_code\",\n            'defaultsDocumentedInCode(num=5, str=\"true\", bool=true, nil=null)\\n',\n        )\n\n    def test_autofunction_variadic(self):\n        \"\"\"Make sure variadic parameters are rendered as ellipses.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_variadic\",\n            \"variadicParameter(a, ...args)\\n\\n   Variadic parameter\\n\",\n        )\n\n    def test_autofunction_deprecated(self):\n        \"\"\"Make sure @deprecated tags can be documented with autofunction.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_deprecated\",\n            \"deprecatedFunction()\\n\\n\"\n            \"   Note:\\n\\n\"\n            \"     Deprecated.\\n\\n\"\n            \"deprecatedExplanatoryFunction()\\n\\n\"\n            \"   Note:\\n\\n\"\n            \"     Deprecated: don't use anymore\\n\",\n        )\n\n    def test_autofunction_see(self):\n        \"\"\"Make sure @see tags work with autofunction.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_see\",\n            \"seeFunction()\\n\\n\"\n            \"   See also:\\n\\n\"\n            '     * \"DeprecatedClass\"\\n\\n'\n            '     * \"deprecatedFunction\"\\n\\n'\n            '     * \"DeprecatedAttribute\"\\n',\n        )\n\n    def test_autofunction_static(self):\n        \"\"\"Make sure the static function gets its prefix ``static``.\"\"\"\n        self._file_contents_eq(\n            \"autofunction_static\",\n            \"class SimpleClass()\\n\\n\"\n            \"   Class doc.\\n\"\n            \"\\n\"\n            \"   SimpleClass.nonStaticMethod()\\n\"\n            \"\\n\"\n            \"      Non-static member.\\n\"\n            \"\\n\"\n            \"      See also:\\n\"\n            \"\\n\"\n            '        * \"staticMethod\"\\n'\n            \"\\n\"\n            \"   static SimpleClass.staticMethod()\\n\"\n            \"\\n\"\n            \"      Static.\\n\"\n            \"\\n\"\n            \"      See also:\\n\"\n            \"\\n\"\n            '        * \"nonStaticMethod\"\\n',\n        )\n\n    def test_autoclass(self):\n        \"\"\"Make sure classes show their class comment and constructor\n        comment.\"\"\"\n        contents = self._file_contents(\"autoclass\")\n        assert \"Class doc.\" in contents\n        assert \"Constructor doc.\" in contents\n\n    def test_autoclass_members(self):\n        \"\"\"Make sure classes list their members if ``:members:`` is specified.\n\n        Make sure it shows both functions and attributes and shows getters and\n        setters as if they are attributes. Make sure it doesn't show private\n        members.\n\n        \"\"\"\n        self._file_contents_eq(\n            \"autoclass_members\",\n            \"class ContainingClass(ho)\\n\\n\"\n            \"   Class doc.\\n\"\n            \"\\n\"\n            \"   Constructor doc.\\n\"\n            \"\\n\"\n            \"   Arguments:\\n\"\n            \"      * **ho** -- A thing\\n\"\n            \"\\n\"\n            \"   ContainingClass.bar\\n\"\n            \"\\n\"\n            \"      Setting this also frobs the frobnicator.\\n\"\n            \"\\n\"\n            \"   ContainingClass.someVar\\n\"\n            \"\\n\"\n            \"      A var\\n\"\n            \"\\n\"\n            \"   ContainingClass.anotherMethod()\\n\"\n            \"\\n\"\n            \"      Another.\\n\"\n            \"\\n\"\n            \"   ContainingClass.someMethod(hi)\\n\"\n            \"\\n\"\n            \"      Here.\\n\"\n            \"\\n\"\n            \"   ContainingClass.yetAnotherMethod()\\n\"\n            \"\\n\"\n            \"      More.\\n\",\n        )\n\n    def test_autoclass_members_list(self):\n        \"\"\"Make sure including a list of names after ``members`` limits it to\n        those names and follows the order you specify.\"\"\"\n        self._file_contents_eq(\n            \"autoclass_members_list\",\n            \"class ClosedClass()\\n\\n   Closed class.\\n\\n   ClosedClass.publical3()\\n\\n      Public thing 3.\\n\\n   ClosedClass.publical()\\n\\n      Public thing.\\n\",\n        )\n\n    def test_autoclass_members_list_star(self):\n        \"\"\"Make sure including ``*`` in a list of names after\n        ``members`` includes the rest of the names in the normal order\n        at that point.\"\"\"\n        self._file_contents_eq(\n            \"autoclass_members_list_star\",\n            \"class ContainingClass(ho)\\n\"\n            \"\\n\"\n            \"   Class doc.\\n\"\n            \"\\n\"\n            \"   Constructor doc.\\n\"\n            \"\\n\"\n            \"   Arguments:\\n\"\n            \"      * **ho** -- A thing\\n\"\n            \"\\n\"\n            \"   ContainingClass.bar\\n\"\n            \"\\n\"\n            \"      Setting this also frobs the frobnicator.\\n\"\n            \"\\n\"\n            \"   ContainingClass.someVar\\n\"\n            \"\\n\"\n            \"      A var\\n\"\n            \"\\n\"\n            \"   ContainingClass.anotherMethod()\\n\"\n            \"\\n\"\n            \"      Another.\\n\"\n            \"\\n\"\n            \"   ContainingClass.yetAnotherMethod()\\n\"\n            \"\\n\"\n            \"      More.\\n\"\n            \"\\n\"\n            \"   ContainingClass.someMethod(hi)\\n\"\n            \"\\n\"\n            \"      Here.\\n\",\n        )\n\n    def test_autoclass_alphabetical(self):\n        \"\"\"Make sure members sort alphabetically when not otherwise specified.\"\"\"\n        self._file_contents_eq(\n            \"autoclass_alphabetical\",\n            \"class NonAlphabetical()\\n\\n   Non-alphabetical class.\\n\\n   NonAlphabetical.a()\\n\\n      Fun a.\\n\\n   NonAlphabetical.z()\\n\\n      Fun z.\\n\",\n        )\n\n    def test_autoclass_private_members(self):\n        \"\"\"Make sure classes list their private members if\n        ``:private-members:`` is specified.\"\"\"\n        contents = self._file_contents(\"autoclass_private_members\")\n        assert \"secret()\" in contents\n\n    def test_autoclass_exclude_members(self):\n        \"\"\"Make sure ``exclude-members`` option actually excludes listed\n        members.\"\"\"\n        contents = self._file_contents(\"autoclass_exclude_members\")\n        assert \"publical()\" in contents\n        assert \"publical2\" not in contents\n        assert \"publical3\" not in contents\n\n    def test_autoclass_example(self):\n        \"\"\"Make sure @example tags can be documented with autoclass.\"\"\"\n        self._file_contents_eq(\n            \"autoclass_example\",\n            \"class ExampleClass()\\n\\n\"\n            \"   JSDoc example tag for class\\n\\n\"\n            \"   Example:\\n\\n\"\n            \"      // This is the example.\\n\"\n            \"      new ExampleClass();\\n\",\n        )\n\n    def test_autoclass_deprecated(self):\n        \"\"\"Make sure @deprecated tags can be documented with autoclass.\"\"\"\n        self._file_contents_eq(\n            \"autoclass_deprecated\",\n            \"class DeprecatedClass()\\n\\n\"\n            \"   Note:\\n\\n\"\n            \"     Deprecated.\\n\\n\"\n            \"class DeprecatedExplanatoryClass()\\n\\n\"\n            \"   Note:\\n\\n\"\n            \"     Deprecated: don't use anymore\\n\",\n        )\n\n    def test_autoclass_see(self):\n        \"\"\"Make sure @see tags work with autoclass.\"\"\"\n        self._file_contents_eq(\n            \"autoclass_see\",\n            \"class SeeClass()\\n\\n\"\n            \"   See also:\\n\\n\"\n            '     * \"DeprecatedClass\"\\n\\n'\n            '     * \"deprecatedFunction\"\\n\\n'\n            '     * \"DeprecatedAttribute\"\\n',\n        )\n\n    def test_autoattribute(self):\n        \"\"\"Make sure ``autoattribute`` works.\"\"\"\n        self._file_contents_eq(\"autoattribute\", \"ContainingClass.someVar\\n\\n   A var\\n\")\n\n    def test_autoattribute_example(self):\n        \"\"\"Make sure @example tags can be documented with autoattribute.\"\"\"\n        self._file_contents_eq(\n            \"autoattribute_example\",\n            \"ExampleAttribute\\n\\n\"\n            \"   JSDoc example tag for attribute\\n\\n\"\n            \"   Example:\\n\\n\"\n            \"      // This is the example.\\n\"\n            \"      console.log(ExampleAttribute);\\n\",\n        )\n\n    def test_autoattribute_deprecated(self):\n        \"\"\"Make sure @deprecated tags can be documented with autoattribute.\"\"\"\n        self._file_contents_eq(\n            \"autoattribute_deprecated\",\n            \"DeprecatedAttribute\\n\\n\"\n            \"   Note:\\n\\n\"\n            \"     Deprecated.\\n\\n\"\n            \"DeprecatedExplanatoryAttribute\\n\\n\"\n            \"   Note:\\n\\n\"\n            \"     Deprecated: don't use anymore\\n\",\n        )\n\n    def test_autoattribute_see(self):\n        \"\"\"Make sure @see tags work with autoattribute.\"\"\"\n        self._file_contents_eq(\n            \"autoattribute_see\",\n            \"SeeAttribute\\n\\n\"\n            \"   See also:\\n\\n\"\n            '     * \"DeprecatedClass\"\\n\\n'\n            '     * \"deprecatedFunction\"\\n\\n'\n            '     * \"DeprecatedAttribute\"\\n',\n        )\n\n    def test_getter_setter(self):\n        \"\"\"Make sure ES6-style getters and setters can be documented.\"\"\"\n        self._file_contents_eq(\n            \"getter_setter\",\n            \"ContainingClass.bar\\n\\n   Setting this also frobs the frobnicator.\\n\",\n        )\n\n    def test_no_shadowing(self):\n        \"\"\"Make sure we can disambiguate objects of the same name.\"\"\"\n        self._file_contents_eq(\n            \"avoid_shadowing\",\n            \"more_code.shadow()\\n\\n   Another thing named shadow, to threaten to shadow the one in\\n   code.js\\n\",\n        )\n\n    def test_restructuredtext_injection(self):\n        \"\"\"Make sure param names and types are escaped and cannot be\n        interpreted as RestructuredText.\n\n        Descriptions should not be escaped; it is a feature to be able to use\n        RST markup there.\n\n        \"\"\"\n        self._file_contents_eq(\n            \"injection\",\n            \"injection(a_, b)\\n\\n\"\n            \"   Arguments:\\n\"\n            \"      * **a_** -- Snorf\\n\\n\"\n            \"      * **b** (**type_**) -- >>Borf_<<\\n\\n\"\n            \"   Returns:\\n\"\n            \"      **rtype_** -- >>Dorf_<<\\n\",\n        )\n\n    def test_union_types(self):\n        \"\"\"Make sure union types render into RST non-wonkily.\n\n        The field was rendering into text as this before::\n\n            * **| Fnode fnodeA** (*Node*) --\n\n        I don't know what RST was thinking, but it got sane again when we\n        switched from \" | \" as the union separator back to \"|\".\n\n        \"\"\"\n        assert \"* **fnodeA** (**Node|Fnode**)\" in self._file_contents(\"union\")\n\n    def test_field_list_unwrapping(self):\n        \"\"\"Ensure the tails of field lists have line breaks and leading\n        whitespace removed.\n\n        Otherwise, the RST parser decides the field list is over, leading to\n        mangled markup.\n\n        \"\"\"\n        self._file_contents_eq(\n            \"unwrapped\",\n            \"longDescriptions(a, b)\\n\"\n            \"\\n\"\n            \"   Once upon a time, there was a large bear named Sid. Sid wore green\\n\"\n            \"   pants with blue stripes and pink polka dots.\\n\"\n            \"\\n\"\n            # Also assert that line breaks in the description are preserved:\n            \"   * List!\\n\"\n            \"\\n\"\n            \"   Arguments:\\n\"\n            \"      * **a** -- A is the first letter of the Roman alphabet. It is\\n\"\n            \"        used in such illustrious words as aardvark and artichoke.\\n\"\n            \"\\n\"\n            \"      * **b** -- Next param, which should be part of the same field\\n\"\n            \"        list\\n\",\n        )\n\n\nDESCRIPTION = \"\"\"\n\n   Return the ratio of the inline text length of the links in an\n   element to the inline text length of the entire element.\"\"\"\n\nFIELDS = \"\"\"\n\n   Arguments:\n      * **node** (**Node**) -- Something of a single type\n\n   Throws:\n      **PartyError|FartyError** -- Something with multiple types and a\n      line that wraps\n\n   Returns:\n      **Number** -- What a thing\n\"\"\"\n\n# Oddly enough, the text renderer renders these bullets with a blank line\n# between, but the HTML renderer does make them a single list.\nCONTENT = \"\"\"\n   Things are \"neat\".\n\n   Off the beat.\n\n   * Sweet\n\n   * Fleet\n\"\"\"\n"
  },
  {
    "path": "tests/test_build_ts/source/class.ts",
    "content": "/**\n * A definition of a class\n */\nexport class ClassDefinition {\n  field: string;\n\n  /**\n   * ClassDefinition constructor\n   * @param simple A parameter with a simple type\n   */\n  constructor(simple: number) {}\n\n  /**\n   * This is a method without return type\n   * @param simple A parameter with a simple type\n   */\n  method1(simple: number): void {}\n\n  /**\n   * This is a method (should be before method 'method1', but after fields)\n   */\n  anotherMethod() {}\n}\n\nexport interface Interface {}\n\nexport abstract class ClassWithSupersAndInterfacesAndAbstract\n  extends ClassDefinition\n  implements Interface\n{\n  /** I construct. */\n  constructor() {\n    super(8);\n  }\n}\n\nexport interface InterfaceWithSupers extends Interface {}\n\nexport class ExportedClass {\n  constructor() {}\n}\n\nexport class ConstructorlessClass {}\n\nexport interface OptionalThings {\n  /**\n   * foop should preserve this documentation\n   */\n  foop?(): void;\n  /**\n   * boop should preserve this documentation\n   */\n  boop?: boolean;\n  /**\n   * noop should preserve this documentation\n   */\n  noop?: () => void;\n}\n\n/**\n * Words words words\n * @param a An optional thing\n * @returns The result\n */\nexport function blah(a: OptionalThings): ConstructorlessClass {\n  return 0 as ConstructorlessClass;\n}\n\nexport function thunk(b: typeof blah) {}\n\n/**\n * A problematic self referential function\n * @param b troublemaker\n */\nexport function selfReferential(b: typeof selfReferential) {}\n\n/**\n * @deprecated since v20!\n */\nexport function deprecatedFunction1() {}\n\n/**\n * @deprecated\n */\nexport function deprecatedFunction2() {}\n\n/**\n * @example This is an example.\n * @example This is another example.\n * ```py\n * Something python\n * ```\n */\nexport function exampleFunction() {}\n\nexport async function asyncFunction() {}\n\nexport class Iterable {\n  *[Symbol.iterator](): Iterator<number, string, undefined> {\n    yield 1;\n    yield 2;\n    yield 3;\n    return \"abc\";\n  }\n}\n\nexport function predicate(c): c is ConstructorlessClass {\n  return false;\n}\n\n/**\n * This shouldn't crash the renderer:\n * ```things```\n */\nexport function weirdCodeInDescription() {}\n\n/**\n * `abc <http://example.com>`_\n */\nexport function spinxLinkInDescription() {}\n\nexport class GetSetDocs {\n  readonly x: number;\n  y: number;\n\n  /**\n   * Getter with comment\n   */\n  get a() {\n    return 7;\n  }\n\n  /**\n   * Setter with comment\n   */\n  set b(x: number) {}\n}\n\nexport class Base {\n  /** Some docs for f */\n  f() {}\n\n  get a() {\n    return 7;\n  }\n}\n\nexport class Extension extends Base {\n  /** Some docs for g */\n  g() {}\n}\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/async_function.rst",
    "content": ".. js:autofunction:: asyncFunction\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/autoclass_class_with_interface_and_supers.rst",
    "content": ".. js:autoclass:: ClassWithSupersAndInterfacesAndAbstract\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/autoclass_constructorless.rst",
    "content": ".. js:autoclass:: ConstructorlessClass\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/autoclass_exported.rst",
    "content": ".. js:autoclass:: ExportedClass\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/autoclass_interface_optionals.rst",
    "content": ".. js:autoclass:: OptionalThings\n   :members:\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/autoclass_star.rst",
    "content": ".. js:autoclass:: ClassDefinition\n   :members: method1, *\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/automodule.rst",
    "content": ".. js:automodule:: module\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/autosummary.rst",
    "content": ".. js:autosummary:: module\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/conf.py",
    "content": "extensions = [\"sphinx_js\"]\n\n# Minimal stuff needed for Sphinx to work:\nsource_suffix = \".rst\"\nmaster_doc = \"index\"\nauthor = \"Erik Rose\"\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\n\njsdoc_config_path = \"../typedoc.json\"\njsdoc_tsconfig_path = \"../tsconfig.json\"\njs_language = \"typescript\"\nfrom sphinx.util import rst\n\nfrom sphinx_js.ir import TypeXRef, TypeXRefInternal\n\n\ndef ts_type_xref_formatter(config, xref: TypeXRef) -> str:\n    if isinstance(xref, TypeXRefInternal):\n        name = rst.escape(xref.name)\n        return f\":js:{xref.kind}:`{name}`\"\n    else:\n        return xref.name\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/deprecated.rst",
    "content": ".. js:autofunction:: deprecatedFunction1\n\n.. js:autofunction:: deprecatedFunction2\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/example.rst",
    "content": ".. js:autofunction:: exampleFunction\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/getset.rst",
    "content": ".. js:autoclass:: GetSetDocs\n   :members:\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/index.rst",
    "content": ".. js:autoclass:: ClassDefinition\n   :members:\n\n   The link from the superclass in autoclass_class_with_interface_and_supers.rst will point here.\n\n.. js:autoclass:: Interface\n   :members:\n\n   Same here.\n\n.. js:autofunction:: weirdCodeInDescription\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/inherited_docs.rst",
    "content": ".. js:autoclass:: Base\n   :members:\n\n.. js:autoclass:: Extension\n   :members:\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/predicate.rst",
    "content": ".. js:autofunction:: predicate\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/sphinx_link_in_description.rst",
    "content": ".. js:autofunction:: spinxLinkInDescription\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/symbol.rst",
    "content": ".. js:autoclass:: Iterable\n   :members:\n"
  },
  {
    "path": "tests/test_build_ts/source/docs/xrefs.rst",
    "content": ".. 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",
    "content": "// Test that we don't crash on a file with no exports\n"
  },
  {
    "path": "tests/test_build_ts/source/module.ts",
    "content": "/**\n * The thing.\n */\nexport const a = 7;\n\n/**\n * Clutches the bundle\n */\nexport async function f() {}\n\nexport function z(a: number, b: typeof q): number {\n  return a;\n}\n\n/**\n * This is a summary. This is more info.\n */\nexport class A {\n  async f() {}\n\n  [Symbol.iterator]() {}\n\n  g(a: number): number {\n    return a + 1;\n  }\n}\n\n/**\n * An instance of class A\n */\nexport let aInstance: A;\n\n/**\n * @typeParam T Description of T\n */\nexport class Z<T extends A> {\n  x: T;\n  constructor(a: number, b: number) {}\n\n  z() {}\n}\n\nexport let zInstance: Z<A>;\n\n/**\n * Another thing.\n */\nexport const q = { a: \"z29\", b: 76 };\n\n/**\n * Documentation for the interface I\n */\nexport interface I {}\n\n/**\n * An instance of the interface\n */\nexport let interfaceInstance: I = {};\n\n/**\n * A super special type alias\n * @typeParam T The whatsit\n */\nexport type TestTypeAlias<T extends A> = { a: T };\nexport type TestTypeAlias2 = { a: number };\n/**\n * Omit from automodule and send summary link somewhere else\n * @omitFromAutoModule\n * @summaryLink :js:typealias:`TestTypeAlias3 <module.TestTypeAlias>`\n */\nexport type TestTypeAlias3 = { a: number };\n\nexport let t: TestTypeAlias<A>;\nexport let t2: TestTypeAlias2;\n\n/**\n * A function with a type parameter!\n *\n * We'll refer to ourselves: :js:func:`~module.functionWithTypeParam`\n *\n * @typeParam T The type parameter\n * @typeParam S Another type param\n * @param z A Z of T\n * @returns The x field of z\n */\nexport function functionWithTypeParam<T extends A>(z: Z<T>): T {\n  return z.x;\n}\n"
  },
  {
    "path": "tests/test_build_ts/source/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es6\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\"\n  }\n}\n"
  },
  {
    "path": "tests/test_build_ts/source/typedoc.json",
    "content": "{}\n"
  },
  {
    "path": "tests/test_build_ts/test_build_ts.py",
    "content": "from textwrap import dedent\n\nfrom bs4 import BeautifulSoup, Tag\n\nfrom tests.testing import SphinxBuildTestCase\n\n\nclass TestTextBuilder(SphinxBuildTestCase):\n    \"\"\"Tests which require our big TS Sphinx tree to be built (as text)\"\"\"\n\n    def test_autoclass_constructor(self):\n        \"\"\"Make sure class constructor comes before methods.\"\"\"\n        contents = self._file_contents(\"index\")\n        pos_cstrct = contents.index(\"ClassDefinition constructor\")\n        pos_method = contents.index(\"ClassDefinition.method1\")\n        assert pos_method > pos_cstrct, (\n            \"Constructor appears after method in \" + contents\n        )\n\n    def test_autoclass_order(self):\n        \"\"\"Make sure fields come before methods.\"\"\"\n        contents = self._file_contents(\"index\")\n        pos_field = contents.index(\"ClassDefinition.field\")\n        pos_method2 = contents.index(\"ClassDefinition.anotherMethod\")\n        pos_method = contents.index(\"ClassDefinition.method1\")\n        assert pos_field < pos_method2 < pos_method, (\n            \"Methods and fields are not in right order in \" + contents\n        )\n\n    def test_autoclass_star_order(self):\n        \"\"\"Make sure fields come before methods even when using ``*``.\"\"\"\n        contents = self._file_contents(\"autoclass_star\")\n        pos_method = contents.index(\"ClassDefinition.method1\")\n        pos_field = contents.index(\"ClassDefinition.field\")\n        pos_method2 = contents.index(\"ClassDefinition.anotherMethod\")\n        assert pos_method < pos_field < pos_method2, (\n            \"Methods and fields are not in right order in \" + contents\n        )\n\n    def test_abstract_extends_and_implements(self):\n        \"\"\"Make sure the template correctly represents abstract classes,\n        classes with superclasses, and classes that implement interfaces.\n\n        These are all TypeScript-specific features.\n\n        \"\"\"\n        # The quotes around ClassDefinition must be some weird decision in\n        # Sphinx's text output. I don't care if they go away in a future\n        # version of Sphinx. It doesn't affect the HTML output.\n        self._file_contents_eq(\n            \"autoclass_class_with_interface_and_supers\",\n            \"class ClassWithSupersAndInterfacesAndAbstract()\\n\"\n            \"\\n\"\n            \"   A definition of a class\\n\\n\"\n            \"   *abstract*\\n\"\n            \"\\n\"\n            '   *exported from* \"class\"\\n'\n            \"\\n\"\n            \"   **Extends:**\\n\"\n            '      * \"ClassDefinition\"\\n'\n            \"\\n\"\n            \"   **Implements:**\\n\"\n            '      * \"Interface\"\\n'\n            \"\\n\"\n            \"   I construct.\\n\",\n        )\n\n    def test_exported_from(self):\n        \"\"\"Make sure classes say where they were exported from.\n\n        I'm divided on whether this is even useful. Maybe people should just\n        specify full.path.Names in the js:autoclass directives if they want to\n        surface that info.\n\n        \"\"\"\n        self._file_contents_eq(\n            \"autoclass_exported\",\n            'class ExportedClass()\\n\\n   *exported from* \"class\"\\n',\n        )\n\n    def test_constructorless_class(self):\n        \"\"\"Make sure constructorless classes don't crash the renderer.\"\"\"\n        self._file_contents_eq(\n            \"autoclass_constructorless\",\n            'class ConstructorlessClass()\\n\\n   *exported from* \"class\"\\n',\n        )\n\n    def test_optional_members(self):\n        \"\"\"Make sure optional attributes and functions of interfaces get\n        question marks sticking out of them.\"\"\"\n        self._file_contents_eq(\n            \"autoclass_interface_optionals\",\n            \"interface OptionalThings\\n\"\n            \"\\n\"\n            '   *exported from* \"class\"\\n'\n            \"\\n\"\n            \"   OptionalThings.boop?\\n\"\n            \"\\n\"\n            \"      type: boolean\\n\"\n            \"\\n\"\n            \"      boop should preserve this documentation\\n\"\n            \"\\n\"\n            \"   OptionalThings.foop?()\\n\"\n            \"\\n\"\n            \"      foop should preserve this documentation\\n\"\n            \"\\n\"\n            \"   OptionalThings.noop?()\\n\"\n            \"\\n\"\n            \"      noop should preserve this documentation\\n\",\n        )\n\n    def test_deprecated(self):\n        self._file_contents_eq(\n            \"deprecated\",\n            dedent(\n                \"\"\"\\\n                deprecatedFunction1()\n\n                   Note:\n\n                     Deprecated: since v20!\n\n                deprecatedFunction2()\n\n                   Note:\n\n                     Deprecated.\n                \"\"\"\n            ),\n        )\n\n    def test_example(self):\n        self._file_contents_eq(\n            \"example\",\n            dedent(\n                \"\"\"\\\n                exampleFunction()\n\n                   Example:\n\n                      This is an example.\n\n                   Example: This is another example.\n\n                      Something python\n                \"\"\"\n            ),\n        )\n\n    def test_async(self):\n        self._file_contents_eq(\n            \"async_function\",\n            dedent(\n                \"\"\"\\\n                async asyncFunction()\n\n                   Returns:\n                      Promise<void>\n                \"\"\"\n            ),\n        )\n\n    def test_symbol(self):\n        expected = dedent(\n            \"\"\"\\\n            class Iterable()\n\n               *exported from* \"class\"\n\n               Iterable.[Symbol․iterator]()\n\n                  Returns:\n                     Iterator<number, string, undefined>\n            \"\"\"\n        )\n        self._file_contents_eq(\"symbol\", expected)\n\n    def test_predicate(self):\n        self._file_contents_eq(\n            \"predicate\",\n            dedent(\n                \"\"\"\\\n                predicate(c)\n\n                   Arguments:\n                      * **c** (any)\n\n                   Returns:\n                      boolean (typeguard for \"ConstructorlessClass\")\n                \"\"\"\n            ),\n        )\n\n    def test_get_set(self):\n        self._file_contents_eq(\n            \"getset\",\n            dedent(\n                \"\"\"\\\nclass GetSetDocs()\n\n   *exported from* \"class\"\n\n   GetSetDocs.a\n\n      type: readonly number\n\n      Getter with comment\n\n   GetSetDocs.b\n\n      type: number\n\n      Setter with comment\n\n   GetSetDocs.x\n\n      type: readonly number\n\n   GetSetDocs.y\n\n      type: number\n                \"\"\"\n            ),\n        )\n\n    def test_inherited_docs(self):\n        # Check that we aren't including documentation entries from the base class\n        self._file_contents_eq(\n            \"inherited_docs\",\n            dedent(\n                \"\"\"\\\n                class Base()\n\n                   *exported from* \"class\"\n\n                   Base.a\n\n                      type: readonly number\n\n                   Base.f()\n\n                      Some docs for f\n\n                class Extension()\n\n                   *exported from* \"class\"\n\n                   **Extends:**\n                      * \"Base\"\n\n                   Extension.g()\n\n                      Some docs for g\n                \"\"\"\n            ),\n        )\n\n    def test_automodule(self):\n        self._file_contents_eq(\n            \"automodule\",\n            dedent(\n                \"\"\"\\\nmodule.TestTypeAlias<T>\n\n   type: { a: T; }\n\n   A super special type alias\n\n   Type parameters:\n      **T** -- The whatsit (extends \"A\")\n\nmodule.TestTypeAlias2\n\n   type: { a: number; }\n\nmodule.a\n\n   type: 7\n\n   The thing.\n\nmodule.aInstance\n\n   type: \"A\"\n\n   An instance of class A\n\nmodule.interfaceInstance\n\n   type: \"I\"\n\n   An instance of the interface\n\nmodule.q\n\n   type: { a: string; b: number; }\n\n   Another thing.\n\nmodule.t\n\n   type: \"TestTypeAlias\"<\"A\">\n\nmodule.t2\n\n   type: \"TestTypeAlias2\"\n\nmodule.zInstance\n\n   type: \"Z\"<\"A\">\n\nasync module.f()\n\n   Clutches the bundle\n\n   Returns:\n      Promise<void>\n\nmodule.functionWithTypeParam<T>(z)\n\n   A function with a type parameter!\n\n   We'll refer to ourselves: \"functionWithTypeParam()\"\n\n   Type parameters:\n      **T** -- The type parameter (extends \"A\")\n\n   Arguments:\n      * **z** (\"Z\"<T>) -- A Z of T\n\n   Returns:\n      T -- The x field of z\n\nmodule.z(a, b)\n\n   Arguments:\n      * **a** (number)\n\n      * **b** ({ a: string; b: number; })\n\n   Returns:\n      number\n\ninterface module.I\n\n   Documentation for the interface I\n\n   *exported from* \"module\"\n\nclass module.A()\n\n   This is a summary. This is more info.\n\n   *exported from* \"module\"\n\n   A.[Symbol․iterator]()\n\n   async A.f()\n\n      Returns:\n         Promise<void>\n\n   A.g(a)\n\n      Arguments:\n         * **a** (number)\n\n      Returns:\n         number\n\nclass module.Z<T>(a, b)\n\n   *exported from* \"module\"\n\n   Type parameters:\n      **T** -- Description of T (extends \"A\")\n\n   Arguments:\n      * **a** (number)\n\n      * **b** (number)\n\n   Z.x\n\n      type: T\n\n   Z.z()\n                \"\"\"\n            ),\n        )\n\n\nclass TestHtmlBuilder(SphinxBuildTestCase):\n    \"\"\"Tests which require an HTML build of our Sphinx tree, for checking\n    links\"\"\"\n\n    builder = \"html\"\n\n    def test_extends_links(self):\n        \"\"\"Make sure superclass mentions link to their definitions.\"\"\"\n        assert 'href=\"index.html#ClassDefinition\"' in self._file_contents(\n            \"autoclass_class_with_interface_and_supers\"\n        )\n\n    def test_implements_links(self):\n        \"\"\"Make sure implemented interfaces link to their definitions.\"\"\"\n        assert 'href=\"index.html#Interface\"' in self._file_contents(\n            \"autoclass_class_with_interface_and_supers\"\n        )\n\n    def test_extends_type_param_links(self):\n        \"\"\"Make sure implemented interfaces link to their definitions.\"\"\"\n        soup = BeautifulSoup(self._file_contents(\"automodule\"), \"html.parser\")\n        z = soup.find(id=\"module.Z\")\n        assert z\n        assert z.parent\n        t = z.parent.find_all(class_=\"sphinx_js-type\")\n        s: Tag = t[0]\n        href: Tag = list(s.children)[0]\n        assert href.name == \"a\"\n        assert href.get_text() == \"A\"\n        assert href.attrs[\"class\"] == [\"reference\", \"internal\"]\n        assert href.attrs[\"title\"] == \"module.A\"\n        assert href.attrs[\"href\"] == \"#module.A\"\n\n    def test_xrefs(self):\n        soup = BeautifulSoup(self._file_contents(\"xrefs\"), \"html.parser\")\n\n        def get_links(id):\n            return soup.find(id=id).parent.find_all(\"a\")\n\n        links = get_links(\"blah\")\n        href = links[1]\n        assert href.attrs[\"class\"] == [\"reference\", \"internal\"]\n        assert href.attrs[\"href\"] == \"autoclass_interface_optionals.html#OptionalThings\"\n        assert href.attrs[\"title\"] == \"OptionalThings\"\n        assert next(href.children).name == \"code\"\n        assert href.get_text() == \"OptionalThings\"\n\n        href = links[2]\n        assert href.attrs[\"class\"] == [\"reference\", \"internal\"]\n        assert (\n            href.attrs[\"href\"] == \"autoclass_constructorless.html#ConstructorlessClass\"\n        )\n        assert href.get_text() == \"ConstructorlessClass\"\n\n        thunk_links = get_links(\"thunk\")\n        assert thunk_links[1].get_text() == \"OptionalThings\"\n        assert thunk_links[2].get_text() == \"ConstructorlessClass\"\n\n    def test_sphinx_link_in_description(self):\n        soup = BeautifulSoup(\n            self._file_contents(\"sphinx_link_in_description\"), \"html.parser\"\n        )\n        href = soup.find(id=\"spinxLinkInDescription\").parent.find_all(\"a\")[1]\n        assert href.get_text() == \"abc\"\n        assert href.attrs[\"href\"] == \"http://example.com\"\n\n    def test_sphinx_js_type_class(self):\n        soup = BeautifulSoup(self._file_contents(\"async_function\"), \"html.parser\")\n        href = soup.find_all(class_=\"sphinx_js-type\")\n        assert len(href) == 1\n        assert href[0].get_text() == \"Promise<void>\"\n\n    def test_autosummary(self):\n        soup = BeautifulSoup(self._file_contents(\"autosummary\"), \"html.parser\")\n        attrs = soup.find(class_=\"attributes\")\n        rows = list(attrs.find_all(\"tr\"))\n        assert len(rows) == 7\n\n        href = rows[0].find(\"a\")\n        assert href.get_text() == \"a\"\n        assert href[\"href\"] == \"automodule.html#module.a\"\n        assert rows[0].find(class_=\"summary\").get_text() == \"The thing.\"\n\n        href = rows[1].find(\"a\")\n        assert href.get_text() == \"aInstance\"\n        assert href[\"href\"] == \"automodule.html#module.aInstance\"\n        assert rows[1].find(class_=\"summary\").get_text() == \"An instance of class A\"\n\n        funcs = soup.find(class_=\"functions\")\n        rows = list(funcs.find_all(\"tr\"))\n        assert len(rows) == 3\n        row0 = list(rows[0].children)\n        NBSP = \"\\xa0\"\n        assert row0[0].get_text() == f\"async{NBSP}f()\"\n        href = row0[0].find(\"a\")\n        assert href.get_text() == \"f\"\n        assert href[\"href\"] == \"automodule.html#module.f\"\n        assert rows[0].find(class_=\"summary\").get_text() == \"Clutches the bundle\"\n\n        row1 = list(rows[2].children)\n        assert row1[0].get_text() == \"z(a, b)\"\n        href = row1[0].find(\"a\")\n        assert href.get_text() == \"z\"\n        assert href[\"href\"] == \"automodule.html#module.z\"\n\n        classes = soup.find(class_=\"classes\")\n        assert classes.find(class_=\"summary\").get_text() == \"This is a summary.\"\n\n        interfaces = soup.find(class_=\"interfaces\")\n        assert (\n            interfaces.find(class_=\"summary\").get_text()\n            == \"Documentation for the interface I\"\n        )\n\n        type_aliases = soup.find(class_=\"type_aliases\")\n        assert type_aliases\n        assert (\n            type_aliases.find(class_=\"summary\").get_text()\n            == \"A super special type alias\"\n        )\n        rows = list(type_aliases.find_all(\"tr\"))\n        assert len(rows) == 3\n        href = rows[2].find(\"a\")\n        assert href.get_text() == \"TestTypeAlias3\"\n        assert href[\"href\"] == \"automodule.html#module.TestTypeAlias\"\n"
  },
  {
    "path": "tests/test_build_xref_none/source/docs/conf.py",
    "content": "extensions = [\"sphinx_js\"]\n\njs_language = \"typescript\"\njs_source_path = [\"../main.ts\"]\nroot_for_relative_js_paths = \"../\"\n\nsuppress_warnings = [\"config.cache\"]\n\n\ndef ts_type_xref_formatter(config, xref):\n    \"\"\"Always return an invalid :js:None: role to test error propagation.\"\"\"\n    return f\":js:None:`{xref.name}`\"\n"
  },
  {
    "path": "tests/test_build_xref_none/source/docs/index.rst",
    "content": "An extra line so we can test whether the error points to the right line.\nAnother extra line.\n\n.. js:autoattribute:: thing\n"
  },
  {
    "path": "tests/test_build_xref_none/source/main.ts",
    "content": "/** A simple variable to document. */\nexport let thing: number;\n"
  },
  {
    "path": "tests/test_build_xref_none/test_build_xref_none.py",
    "content": "\"\"\"\nRST errors from ts_type_xref_formatter should cause build failures.\n\nThe test conf.py uses a formatter that always returns :js:None:`...`, which is\nan invalid RST role. This should cause the build to fail.\n\nWe also test that the error message points to the right location.\n\"\"\"\n\nimport io\nfrom contextlib import redirect_stderr\nfrom pathlib import Path\n\nfrom sphinx.cmd.build import main as sphinx_main\n\n\ndef test_build_fails_with_invalid_role(tmp_path: Path):\n    \"\"\"Build must fail when ts_type_xref_formatter emits an invalid RST role.\"\"\"\n    docs_dir = str(Path(__file__).parent / \"source\" / \"docs\")\n    stderr = io.StringIO()\n    with redirect_stderr(stderr):\n        result = sphinx_main([docs_dir, \"-b\", \"text\", \"-W\", \"-E\", str(tmp_path)])\n    output = stderr.getvalue()\n    assert result != 0, \"Expected build failure due to invalid :js:None: role\"\n    assert \"index.rst:4\" in output, f\"Expected error at index.rst:4, got: {output}\"\n"
  },
  {
    "path": "tests/test_common_mark/source/code.js",
    "content": "/**\n * Foo.\n */\nfunction foo() {}\n"
  },
  {
    "path": "tests/test_common_mark/source/docs/conf.py",
    "content": "from recommonmark.transform import AutoStructify\n\nextensions = [\"recommonmark\", \"sphinx.ext.mathjax\", \"sphinx_js\"]\nsource_suffix = [\".rst\", \".md\"]\nmaster_doc = \"index\"\nauthor = \"Jam Risser\"\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\n\n\ndef setup(app):\n    app.add_config_value(\n        \"recommonmark_config\",\n        {\n            \"auto_toc_tree_section\": \"Content\",\n            \"enable_auto_doc_ref\": True,\n            \"enable_auto_toc_tree\": True,\n            \"enable_eval_rst\": True,\n            \"enable_inline_math\": True,\n            \"enable_math\": True,\n        },\n        True,\n    )\n    app.add_transform(AutoStructify)\n"
  },
  {
    "path": "tests/test_common_mark/source/docs/index.md",
    "content": "```eval_rst\n.. js:autofunction:: foo\n```\n"
  },
  {
    "path": "tests/test_common_mark/test_common_mark.py",
    "content": "from tests.testing import SphinxBuildTestCase\n\n\nclass Tests(SphinxBuildTestCase):\n    def test_build_success(self):\n        \"\"\"Mostly just test that the build doesn't crash, which is what used to\n        happen before we added the tab_width workaround.\n\n        \"\"\"\n        self._file_contents_eq(\"index\", \"foo()\\n\\n   Foo.\\n\")\n"
  },
  {
    "path": "tests/test_dot_dot_paths/source/code.js",
    "content": "/**\n * Bar function\n */\nfunction bar(node) {}\n"
  },
  {
    "path": "tests/test_dot_dot_paths/source/docs/conf.py",
    "content": "extensions = [\"sphinx_js\"]\n\n# Minimal stuff needed for Sphinx to work:\nsource_suffix = \".rst\"\nmaster_doc = \"index\"\nauthor = \"Erik Rose\"\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\nroot_for_relative_js_paths = \"./\"\n"
  },
  {
    "path": "tests/test_dot_dot_paths/source/docs/index.rst",
    "content": ".. js:autofunction:: bar\n"
  },
  {
    "path": "tests/test_dot_dot_paths/test_dot_dot_paths.py",
    "content": "from tests.testing import SphinxBuildTestCase\n\n\nclass Tests(SphinxBuildTestCase):\n    def test_dot_dot(self):\n        \"\"\"Make sure the build doesn't explode with a parse error on\n        the \"../more.bar\" path that is constructed when the JSDoc doclets are\n        imbibed.\n\n        Also make sure it render correctly afterward.\n\n        \"\"\"\n        self._file_contents_eq(\"index\", \"bar(node)\\n\\n   Bar function\\n\")\n"
  },
  {
    "path": "tests/test_incremental.py",
    "content": "\"\"\"Test incremental builds.\"\"\"\n\nimport warnings\nfrom pathlib import Path\n\nimport pytest\nfrom sphinx.environment import CONFIG_NEW, CONFIG_OK\n\ntry:\n    from sphinx.util.console import strip_colors\nexcept ImportError:\n    from sphinx.testing.util import strip_escseq as strip_colors\n\n\ndef build(app):\n    \"\"\"Build the given app, collecting docnames read and written (resolved).\n\n    Returns a tuple (status text, [reads], [writes]), with reads and writes\n    sorted for convenience.\n\n    \"\"\"\n    reads = set([])\n    writes = set([])\n\n    def source_read(app, docname, source):\n        reads.add(docname)\n\n    def doctree_resolved(app, doctree, docname):\n        writes.add(docname)\n\n    source_read_id = app.connect(\"source-read\", source_read)\n    doctree_resolved_id = app.connect(\"doctree-resolved\", doctree_resolved)\n\n    try:\n        with warnings.catch_warnings():\n            warnings.filterwarnings(action=\"ignore\", category=DeprecationWarning)\n            app.build()\n    finally:\n        app.disconnect(source_read_id)\n        app.disconnect(doctree_resolved_id)\n\n    return (\n        strip_colors(app._status.getvalue()),\n        list(sorted(reads)),\n        list(sorted(writes)),\n    )\n\n\ndef do_test(app, extension=\"js\"):\n    # Clean build.\n    assert app.env.config_status == CONFIG_NEW\n    status, reads, writes = build(app)\n    assert reads == [\"a\", \"a_b\", \"b\", \"index\", \"unrelated\"]\n    assert writes == [\"a\", \"a_b\", \"b\", \"index\", \"unrelated\"]\n\n    # Incremental build, no config changed and no files changed.\n    assert app.env.config_status == CONFIG_OK\n    status, reads, writes = build(app)\n    assert reads == []\n    assert writes == []\n\n    # Incremental build, one file changed.\n    a_js = Path(app.srcdir) / f\"a.{extension}\"\n    a_js.write_text(a_js.read_text() + \"\\n\\n\")\n\n    assert app.env.config_status == CONFIG_OK\n    status, reads, writes = build(app)\n    # FIXME: re-enable the rest of this test!\n    return\n\n    assert reads == [\"a\", \"a_b\"]\n    # Note that the transitive dependency 'index' is written.\n    assert writes == [\"a\", \"a_b\", \"index\"]\n\n    # Incremental build, the other file changed.\n    b_js = Path(app.srcdir) / \"inner\" / f\"b.{extension}\"\n    b_js.write_text(b_js.read_text() + \"\\n\\n\")\n\n    assert app.env.config_status == CONFIG_OK\n    status, reads, writes = build(app)\n    assert reads == [\"a_b\", \"b\"]\n    # Note that the transitive dependency 'index' is written.\n    assert writes == [\"a_b\", \"b\", \"index\"]\n\n\n# We must use a \"real\" builder since the `dummy` builder does not track changed\n# files.\n@pytest.mark.sphinx(\"html\", testroot=\"incremental_js\")\ndef test_incremental_js(make_app, app_params):\n    args, kwargs = app_params\n\n    app = make_app(*args, freshenv=True, **kwargs)\n    do_test(app, extension=\"js\")\n\n\n@pytest.mark.xfail(reason=\"TODO: fix me!\")\n@pytest.mark.sphinx(\"html\", testroot=\"incremental_ts\")\ndef test_incremental_ts(make_app, app_params):\n    args, kwargs = app_params\n\n    app = make_app(*args, freshenv=True, **kwargs)\n    do_test(app, extension=\"ts\")\n"
  },
  {
    "path": "tests/test_init.py",
    "content": "import pytest\nfrom sphinx.errors import SphinxError\n\nfrom sphinx_js import root_or_fallback\n\n\ndef test_relative_path_root():\n    \"\"\"Make sure the computation of the root path for relative JS entity\n    pathnames is right.\"\"\"\n    # Fall back to the only source path if not specified.\n    assert root_or_fallback(None, [\"a\"]) == \"a\"\n    with pytest.raises(SphinxError):\n        root_or_fallback(None, [\"a\", \"b\"])\n    assert root_or_fallback(\"smoo\", [\"a\"]) == \"smoo\"\n"
  },
  {
    "path": "tests/test_ir.py",
    "content": "from inspect import getmembers\nfrom json import dumps, loads\n\nimport pytest\n\nfrom sphinx_js.ir import (\n    Attribute,\n    DescriptionCode,\n    DescriptionName,\n    DescriptionText,\n    Function,\n    Param,\n    Pathname,\n    Return,\n    TopLevel,\n    TypeXRefExternal,\n    TypeXRefInternal,\n    converter,\n    json_to_ir,\n)\n\n\ndef test_default():\n    \"\"\"Accessing ``.default`` on a Param having a default should return the\n    default value.\"\"\"\n    p = Param(name=\"fred\", has_default=True, default=\"boof\")\n    assert p.default == \"boof\"\n\n\ndef test_missing_default():\n    \"\"\"Constructing a Param with ``has_default=True`` but without a ``default``\n    value should raise an error.\"\"\"\n    with pytest.raises(ValueError):\n        Param(name=\"fred\", has_default=True)\n\n\ntop_level_base = TopLevel(\n    name=\"blah\",\n    block_tags={},\n    deppath=\"x\",\n    deprecated=False,\n    description=[],\n    examples=[],\n    exported_from=Pathname([]),\n    filename=\"\",\n    line=7,\n    modifier_tags=[],\n    path=Pathname([]),\n    properties=[],\n    see_alsos=[],\n    kind=\"\",\n)\ntl_dict = {k: v for k, v in getmembers(top_level_base) if not k.startswith(\"_\")}\ndel tl_dict[\"kind\"]\n\nattribute_base = Attribute(\n    **tl_dict,\n    is_abstract=False,\n    is_optional=False,\n    is_static=False,\n    is_private=False,\n    readonly=False,\n    type=[],\n)\nattr_dict = {k: v for k, v in getmembers(attribute_base) if not k.startswith(\"_\")}\n\n\ndef attr_with(**kwargs):\n    return Attribute(**(attr_dict | kwargs))\n\n\nfunction_base = Function(\n    **tl_dict,\n    is_abstract=False,\n    is_optional=False,\n    is_static=False,\n    is_private=False,\n    is_async=False,\n    params=[],\n    exceptions=[],\n    returns=[],\n)\nfunc_dict = {k: v for k, v in getmembers(function_base) if not k.startswith(\"_\")}\n\n\ndef func_with(**kwargs):\n    return Function(**(func_dict | kwargs))\n\n\n# Check that we can successfully serialize and desrialize IR.\n\n\n@pytest.mark.parametrize(\n    \"x\",\n    [\n        attr_with(),\n        attr_with(type=\"a string\"),\n        attr_with(\n            type=[\n                TypeXRefInternal(\"xx\", [\"a\", \"b\"]),\n                \"x\",\n                TypeXRefExternal(\"blah\", \"pkg\", \"sfn\", \"qn\"),\n            ]\n        ),\n        attr_with(\n            deprecated=True,\n        ),\n        attr_with(\n            deprecated=\"a string\",\n        ),\n        attr_with(\n            deprecated=[\n                DescriptionName(\"name\"),\n                DescriptionText(\"xx\"),\n                DescriptionCode(\"yy\"),\n            ],\n        ),\n        func_with(),\n        func_with(params=[Param(name=\"fred\", has_default=True, default=\"boof\")]),\n        func_with(\n            params=[Param(name=\"fred\", has_default=False)],\n            returns=[\n                Return(\n                    type=[TypeXRefInternal(\"a\", [])], description=[DescriptionText(\"x\")]\n                )\n            ],\n        ),\n    ],\n)\ndef test_ir_serialization(x):\n    l = [x]\n    s = converter.unstructure(l)\n    s2 = loads(dumps(s))\n    assert s == s2\n    l2 = json_to_ir(s2)\n    assert l2 == l\n"
  },
  {
    "path": "tests/test_jsdoc_analysis/source/class.js",
    "content": "/**\n * This is a long description that should not be unwrapped. Once day, I was\n * walking down the street, and a large, green, polka-dotted grand piano fell\n * from the 23rd floor of an apartment building.\n *\n * @example Example in class\n */\nclass Foo {\n  /**\n   * Constructor doc.\n   *\n   * @arg ho A thing\n   * @example Example in constructor\n   */\n  constructor(ho) {}\n\n  /**\n   * Setting this also frobs the frobnicator.\n   */\n  get bar() {\n    return this._bar;\n  }\n  set bar(baz) {\n    this._bar = _bar;\n  }\n\n  /**\n   * Private method.\n   *\n   * @private\n   */\n  secret() {}\n}\n"
  },
  {
    "path": "tests/test_jsdoc_analysis/source/function.js",
    "content": "/**\n * Determine any of type, note, score, and element using a callback. This\n * overrides any previous call.\n *\n * The callback should return...\n *\n * * An optional :term:`subscore`\n * * A type (required on ``dom(...)`` rules, defaulting to the input one on\n *   ``type(...)`` rules)\n *\n * @param {String} bar Which bar\n * @param baz\n * @return {Number} How many things\n *     there are\n * @exception ExplosionError It went boom.\n */\nfunction foo(bar, baz = 8) {}\n"
  },
  {
    "path": "tests/test_jsdoc_analysis/test_jsdoc.py",
    "content": "from sphinx_js.ir import (\n    Attribute,\n    DescriptionCode,\n    Exc,\n    Function,\n    Param,\n    Pathname,\n    Return,\n)\nfrom sphinx_js.jsdoc import full_path_segments\nfrom tests.testing import JsDocTestCase\n\n\ndef test_doclet_full_path():\n    \"\"\"Sanity-check full_path_segments(), including throwing it a non-.js filename.\"\"\"\n    doclet = {\n        \"meta\": {\n            \"filename\": \"utils.jsm\",\n            \"path\": \"/boogie/smoo/Checkouts/fathom\",\n        },\n        \"longname\": \"best#thing~yeah\",\n    }\n    assert full_path_segments(doclet, \"/boogie/smoo/Checkouts\") == [\n        \"./\",\n        \"fathom/\",\n        \"utils.\",\n        \"best#\",\n        \"thing~\",\n        \"yeah\",\n    ]\n\n\nclass TestFunction(JsDocTestCase):\n    file = \"function.js\"\n\n    def test_top_level_and_function(self):\n        \"\"\"Test Function (and thus also TopLevel) analysis.\n\n        This also includes exceptions, returns, params, and default values.\n\n        \"\"\"\n        function = self.analyzer.get_object([\"foo\"], \"function\")\n        assert function == Function(\n            name=\"foo\",\n            path=Pathname([\"./\", \"function.\", \"foo\"]),\n            filename=\"function.js\",\n            deppath=\"function.js\",\n            # Line breaks and indentation should be preserved:\n            description=(\n                \"Determine any of type, note, score, and element using a callback. This\\n\"\n                \"overrides any previous call.\\n\"\n                \"\\n\"\n                \"The callback should return...\\n\"\n                \"\\n\"\n                \"* An optional :term:`subscore`\\n\"\n                \"* A type (required on ``dom(...)`` rules, defaulting to the input one on\\n\"\n                \"  ``type(...)`` rules)\"\n            ),\n            line=17,\n            deprecated=False,\n            examples=[],\n            see_alsos=[],\n            properties=[],\n            is_private=False,\n            exported_from=None,\n            is_abstract=False,\n            is_optional=False,\n            is_static=False,\n            is_async=False,\n            params=[\n                Param(\n                    name=\"bar\",\n                    description=\"Which bar\",\n                    has_default=False,\n                    is_variadic=False,\n                    type=\"String\",\n                ),\n                Param(\n                    name=\"baz\",\n                    description=\"\",\n                    has_default=True,\n                    default=\"8\",\n                    is_variadic=False,\n                    type=None,\n                ),\n            ],\n            exceptions=[Exc(type=None, description=\"ExplosionError It went boom.\")],\n            returns=[\n                Return(\n                    type=\"Number\",\n                    # Line breaks and indentation should be preserved:\n                    description=\"How many things\\n    there are\",\n                )\n            ],\n        )\n\n\nclass TestClass(JsDocTestCase):\n    file = \"class.js\"\n\n    def test_class(self):\n        \"\"\"Test Class analysis, including members, attributes, and privacy.\"\"\"\n        cls = self.analyzer.get_object([\"Foo\"], \"class\")\n        assert cls.name == \"Foo\"\n        assert cls.path == Pathname([\"./\", \"class.\", \"Foo\"])\n        assert cls.filename == \"class.js\"\n        assert (\n            cls.description\n            == \"This is a long description that should not be unwrapped. Once day, I was\\nwalking down the street, and a large, green, polka-dotted grand piano fell\\nfrom the 23rd floor of an apartment building.\"\n        )\n        # Not ideal, as it refers to the constructor, but we'll allow it\n        assert cls.line in (\n            8,  # jsdoc 4.0.0\n            15,  # jsdoc 3.6.3\n        )\n        # We ignore examples and other fields from the class doclet so far. This could change someday.\n        assert cls.examples == [[DescriptionCode(code=\"```js\\nExample in class\\n```\")]]\n\n        # Members:\n        getter, private_method = cls.members  # default constructor not included here\n        assert isinstance(private_method, Function)\n        assert private_method.name == \"secret\"\n        assert private_method.path == Pathname([\"./\", \"class.\", \"Foo#\", \"secret\"])\n        assert private_method.description == \"Private method.\"\n        assert private_method.is_private is True\n        assert isinstance(getter, Attribute)\n        assert getter.name == \"bar\"\n        assert getter.path == Pathname([\"./\", \"class.\", \"Foo#\", \"bar\"])\n        assert getter.filename == \"class.js\"\n        assert getter.description == \"Setting this also frobs the frobnicator.\"\n\n        # Constructor:\n        constructor = cls.constructor_\n        assert constructor.name == \"Foo\"\n        assert constructor.path == Pathname(\n            [\"./\", \"class.\", \"Foo\"]\n        )  # Same path as class. This might differ in different languages.\n        assert constructor.filename == \"class.js\"\n        assert constructor.description == \"Constructor doc.\"\n        assert constructor.examples == [\n            [DescriptionCode(code=\"```js\\nExample in class\\n```\")]\n        ]\n        assert constructor.params == [\n            Param(\n                name=\"ho\",\n                description=\"A thing\",\n                has_default=False,\n                is_variadic=False,\n                type=None,\n            )\n        ]\n"
  },
  {
    "path": "tests/test_parsers.py",
    "content": "from sphinx_js.parsers import PathVisitor, path_and_formal_params\n\n\ndef test_escapes():\n    r\"\"\"Make sure escapes work right and Python escapes like \\n don't work.\"\"\"\n    assert PathVisitor().parse(r\"d\\.i:\\nr/ut\\\\ils.max(whatever)\") == [\n        [r\"d.i:nr/\", r\"ut\\ils.\", \"max\"],\n        \"(whatever)\",\n    ]\n\n\ndef test_relative_dirs():\n    \"\"\"Make sure all sorts of relative-dir prefixes result in proper path\n    segment arrays.\"\"\"\n    assert PathVisitor().visit(path_and_formal_params[\"path\"].parse(\"./hi\")) == [\n        \"./\",\n        \"hi\",\n    ]\n    assert PathVisitor().visit(path_and_formal_params[\"path\"].parse(\"../../hi\")) == [\n        \"../\",\n        \"../\",\n        \"hi\",\n    ]\n"
  },
  {
    "path": "tests/test_paths.py",
    "content": "import subprocess\nfrom pathlib import Path\n\nimport pytest\nfrom conftest import TYPEDOC_VERSION\nfrom sphinx.errors import SphinxError\n\nfrom sphinx_js.analyzer_utils import search_node_modules\n\n\n@pytest.fixture(autouse=True)\ndef clear_node_modules_env(monkeypatch):\n    monkeypatch.delenv(\"SPHINX_JS_NODE_MODULES\")\n\n\n@pytest.fixture\ndef global_install(tmp_path_factory, monkeypatch):\n    tmpdir = tmp_path_factory.mktemp(\"my_program_global\")\n    my_program = tmpdir / \"my_program\"\n    my_program.write_text(\"\")\n    my_program.chmod(0o777)\n    monkeypatch.setenv(\"PATH\", str(tmpdir), prepend=\":\")\n    return tmpdir\n\n\n@pytest.fixture\ndef no_local_install(tmp_path_factory):\n    my_program = tmp_path_factory.mktemp(\"my_program_local\")\n    working_dir = my_program / \"a\" / \"b\" / \"c\"\n    return working_dir\n\n\nmy_prog_path = Path(\"my_program/sub/bin.js\")\n\n\n@pytest.fixture\ndef local_install(no_local_install):\n    working_dir = no_local_install\n    bin_path = working_dir.parents[1] / \"node_modules\" / my_prog_path\n    bin_path.parent.mkdir(parents=True)\n    bin_path.write_text(\"\")\n    return (working_dir, bin_path)\n\n\n@pytest.fixture\ndef env_install(monkeypatch):\n    env_path = Path(\"/a/b/c\")\n    monkeypatch.setenv(\"SPHINX_JS_NODE_MODULES\", str(env_path))\n    return env_path / my_prog_path\n\n\ndef test_global(global_install, no_local_install):\n    # If no env or local, use global\n    working_dir = no_local_install\n    assert search_node_modules(\"my_program\", my_prog_path, working_dir) == str(\n        global_install / \"my_program\"\n    )\n\n\ndef test_node_modules1(global_install, local_install):\n    # If local and global, use local\n    [working_dir, bin_path] = local_install\n    assert search_node_modules(\"my_program\", my_prog_path, working_dir) == str(bin_path)\n\n\ndef test_node_modules2(local_install):\n    # If local only, use local\n    [working_dir, bin_path] = local_install\n    assert search_node_modules(\"my_program\", my_prog_path, working_dir) == str(bin_path)\n\n\ndef test_env1(env_install):\n    # If env only, use env\n    assert search_node_modules(\"my_program\", my_prog_path, \"/x/y/z\") == str(env_install)\n\n\ndef test_env2(env_install, local_install, global_install):\n    # If env, local, and global, use env\n    [working_dir, _] = local_install\n    assert search_node_modules(\"my_program\", my_prog_path, working_dir) == str(\n        env_install\n    )\n\n\ndef test_err():\n    with pytest.raises(\n        SphinxError,\n        match='my_program was not found. Install it using \"npm install my_program\"',\n    ):\n        search_node_modules(\"my_program\", my_prog_path, \"/a/b/c\")\n\n\n@pytest.mark.xfail(reason=\"Isn't working right now. Not sure why.\")\ndef test_global_install(tmp_path_factory, monkeypatch):\n    tmpdir = tmp_path_factory.mktemp(\"global_root\")\n    tmpdir2 = tmp_path_factory.mktemp(\"blah\")\n    monkeypatch.setenv(\"npm_config_prefix\", str(tmpdir))\n    monkeypatch.setenv(\"PATH\", str(tmpdir / \"bin\"), prepend=\":\")\n    version = \".\".join(str(x) for x in TYPEDOC_VERSION)\n    subprocess.run([\"npm\", \"i\", \"-g\", f\"typedoc@{version}\", \"typescript\"])\n    typedoc = search_node_modules(\"typedoc\", \"typedoc/bin/typedoc\", str(tmpdir2))\n    monkeypatch.setenv(\"TYPEDOC_NODE_MODULES\", str(Path(typedoc).parents[3]))\n    dir = Path(__file__).parents[1].resolve() / \"sphinx_js/js\"\n\n    res = subprocess.run(\n        [\n            \"npx\",\n            \"tsx@4.15.8\",\n            \"--import\",\n            dir / \"registerImportHook.mjs\",\n            dir / \"main.ts\",\n            \"--version\",\n        ],\n        capture_output=True,\n        encoding=\"utf8\",\n    )\n    print(res.stdout)\n    print(res.stderr)\n    res.check_returncode()\n    assert f\"TypeDoc {version}\" in res.stdout\n"
  },
  {
    "path": "tests/test_renderers.py",
    "content": "from textwrap import dedent, indent\nfrom typing import Any\n\nimport pytest\nfrom sphinx.util import rst\n\nfrom sphinx_js.ir import (\n    Attribute,\n    Class,\n    DescriptionCode,\n    DescriptionText,\n    Exc,\n    Function,\n    Interface,\n    Module,\n    Param,\n    Return,\n    TypeAlias,\n    TypeParam,\n    TypeXRefExternal,\n    TypeXRefInternal,\n)\nfrom sphinx_js.renderers import (\n    AutoAttributeRenderer,\n    AutoFunctionRenderer,\n    AutoModuleRenderer,\n    render_description,\n)\n\n\ndef setindent(txt):\n    return indent(dedent(txt), \" \" * 3)\n\n\ndef test_render_description():\n    assert render_description(\n        [\n            DescriptionText(text=\"Code 1 had \"),\n            DescriptionCode(code=\"`single ticks around it`\"),\n            DescriptionText(text=\".\\nCode 2 has \"),\n            DescriptionCode(code=\"``double ticks around it``\"),\n            DescriptionText(text=\".\\nCode 3 has a :sphinx:role:\"),\n            DescriptionCode(code=\"`before it`\"),\n            DescriptionText(text=\".\\n\\n\"),\n            DescriptionCode(code=\"```js\\nA JS code pen!\\n```\"),\n            DescriptionText(text=\"\\nAnd some closing words.\"),\n        ]\n    ) == dedent(\n        \"\"\"\\\n        Code 1 had ``single ticks around it``.\n        Code 2 has ``double ticks around it``.\n        Code 3 has a :sphinx:role:`before it`.\n\n\n        .. code-block:: js\n\n            A JS code pen!\n\n\n\n        And some closing words.\"\"\"\n    )\n\n\ndef ts_xref_formatter(config, xref):\n    if isinstance(xref, TypeXRefInternal):\n        name = rst.escape(xref.name)\n        return f\":js:{xref.kind}:`{name}`\"\n    else:\n        return xref.name\n\n\ndef make_renderer(cls):\n    class _app:\n        class config:\n            ts_type_xref_formatter = ts_xref_formatter\n\n    renderer = cls.__new__(cls)\n    renderer._app = _app\n    renderer._explicit_formal_params = None\n    renderer._content = []\n    renderer._set_type_xref_formatter(ts_xref_formatter)\n    renderer._add_span = False\n    return renderer\n\n\n@pytest.fixture()\ndef function_renderer():\n    def lookup_object(self, partial_path: list[str]):\n        return self.objects[partial_path[-1]]\n\n    renderer = make_renderer(AutoFunctionRenderer)\n    renderer.lookup_object = lookup_object.__get__(renderer)\n    renderer.objects = {}\n    return renderer\n\n\n@pytest.fixture()\ndef attribute_renderer():\n    return make_renderer(AutoAttributeRenderer)\n\n\n@pytest.fixture()\ndef auto_module_renderer():\n    renderer = make_renderer(AutoModuleRenderer)\n\n    class directive:\n        class state:\n            class document:\n                class settings:\n                    pass\n\n    renderer._directive = directive\n    return renderer\n\n\n@pytest.fixture()\ndef function_render(function_renderer) -> Any:\n    def function_render(partial_path=None, use_short_name=False, objects=None, **args):\n        if objects is None:\n            objects = {}\n        if not partial_path:\n            partial_path = [\"blah\"]\n        function_renderer.objects = objects\n        return function_renderer.rst(\n            partial_path, make_function(**args), use_short_name\n        )\n\n    return function_render\n\n\n@pytest.fixture()\ndef attribute_render(attribute_renderer) -> Any:\n    def attribute_render(partial_path=None, use_short_name=False, **args):\n        if not partial_path:\n            partial_path = [\"blah\"]\n        return attribute_renderer.rst(\n            partial_path, make_attribute(**args), use_short_name\n        )\n\n    return attribute_render\n\n\n@pytest.fixture()\ndef type_alias_render(attribute_renderer) -> Any:\n    def type_alias_render(partial_path=None, use_short_name=False, **args):\n        if not partial_path:\n            partial_path = [\"blah\"]\n        return attribute_renderer.rst(\n            partial_path, make_type_alias(**args), use_short_name\n        )\n\n    return type_alias_render\n\n\n@pytest.fixture()\ndef auto_module_render(auto_module_renderer) -> Any:\n    def auto_module_render(partial_path=None, use_short_name=False, **args):\n        if not partial_path:\n            partial_path = [\"blah\"]\n        return auto_module_renderer.rst(\n            partial_path, make_module(**args), use_short_name\n        )\n\n    return auto_module_render\n\n\ntop_level_dict = dict(\n    name=\"\",\n    path=[],\n    filename=\"\",\n    deppath=\"\",\n    description=\"\",\n    line=0,\n    deprecated=\"\",\n    examples=[],\n    see_alsos=[],\n    properties=[],\n    exported_from=None,\n)\n\nmember_dict = dict(\n    is_abstract=False,\n    is_optional=False,\n    is_static=False,\n    is_private=False,\n)\n\nmembers_and_supers_dict = dict(members=[], supers=[])\n\nclass_dict = (\n    top_level_dict\n    | members_and_supers_dict\n    | dict(constructor_=None, is_abstract=False, interfaces=[], type_params=[])\n)\ninterface_dict = top_level_dict | members_and_supers_dict | dict(type_params=[])\nfunction_dict = (\n    top_level_dict\n    | member_dict\n    | dict(\n        is_async=False,\n        params=[],\n        exceptions=[],\n        returns=[],\n    )\n)\nattribute_dict = top_level_dict | member_dict | dict(type=\"\")\ntype_alias_dict = top_level_dict | dict(type=\"\", type_params=[])\nmodule_dict = dict(\n    filename=\"\",\n    deppath=None,\n    path=[],\n    line=0,\n    attributes=[],\n    functions=[],\n    classes=[],\n    interfaces=[],\n    type_aliases=[],\n)\n\n\ndef make_class(**args):\n    return Class(**(class_dict | args))\n\n\ndef make_interface(**args):\n    return Interface(**(interface_dict | args))\n\n\ndef make_function(**args):\n    return Function(**(function_dict | args))\n\n\ndef make_attribute(**args):\n    return Attribute(**(attribute_dict | args))\n\n\ndef make_type_alias(**args):\n    return TypeAlias(**(type_alias_dict | args))\n\n\ndef make_module(**args):\n    return Module(**(module_dict | args))\n\n\nDEFAULT_RESULT = \".. js:function:: blah()\\n\"\n\n\ndef test_func_render_simple(function_render):\n    assert function_render() == DEFAULT_RESULT\n\n\ndef test_func_render_shortnames(function_render):\n    assert function_render([\"a.\", \"b.\", \"c\"]) == \".. js:function:: a.b.c()\\n\"\n    assert (\n        function_render([\"a.\", \"b.\", \"c\"], use_short_name=True)\n        == \".. js:function:: c()\\n\"\n    )\n\n\ndef test_func_render_flags(function_render):\n    # is_abstract is ignored? Maybe only makes sense if it is a class method??\n    # TODO: look into this.\n    assert function_render(is_abstract=True) == DEFAULT_RESULT\n    assert function_render(is_optional=True) == \".. js:function:: blah?()\\n\"\n    assert function_render(is_static=True) == \".. js:function:: blah()\\n   :static:\\n\"\n    assert function_render(is_async=True) == \".. js:function:: blah()\\n   :async:\\n\"\n    assert (\n        function_render(is_async=True, is_static=True)\n        == \".. js:function:: blah()\\n   :static:\\n   :async:\\n\"\n    )\n    assert function_render(is_private=True) == DEFAULT_RESULT\n\n\ndef test_func_render_description(function_render):\n    assert function_render(\n        description=\"this is a description\"\n    ) == DEFAULT_RESULT + setindent(\n        \"\"\"\n        this is a description\n        \"\"\",\n    )\n\n\ndef test_func_render_params(function_render):\n    assert function_render(\n        description=\"this is a description\",\n        params=[Param(\"a\", description=\"a description\")],\n    ) == dedent(\n        \"\"\"\\\n        .. js:function:: blah(a)\n\n           this is a description\n\n           :param a: a description\n        \"\"\"\n    )\n    assert function_render(\n        description=\"this is a description\",\n        params=[Param(\"a\", description=\"a description\"), Param(\"b\", \"b description\")],\n    ) == dedent(\n        \"\"\"\\\n        .. js:function:: blah(a, b)\n\n           this is a description\n\n           :param a: a description\n           :param b: b description\n        \"\"\"\n    )\n\n\ndef test_func_render_returns(function_render):\n    assert function_render(\n        params=[Param(\"a\", description=\"a description\"), Param(\"b\", \"b description\")],\n        returns=[Return(\"number\", \"first thing\"), Return(\"string\", \"second thing\")],\n    ) == dedent(\n        \"\"\"\\\n        .. js:function:: blah(a, b)\n\n           :param a: a description\n           :param b: b description\n           :returns: **number** -- first thing\n           :returns: **string** -- second thing\n        \"\"\"\n    )\n\n\ndef test_func_render_type_params(function_render):\n    assert function_render(\n        params=[Param(\"a\", type=\"T\"), Param(\"b\", type=\"S\")],\n        type_params=[\n            TypeParam(\"T\", \"number\", \"a type param\"),\n            TypeParam(\"S\", \"\", \"second type param\"),\n        ],\n    ) == dedent(\n        \"\"\"\\\n        .. js:function:: blah<T, S>(a, b)\n\n           :typeparam T: a type param (extends **number**)\n           :typeparam S: second type param\n           :param a:\n           :param b:\n           :type a: **T**\n           :type b: **S**\n        \"\"\"\n    )\n\n\ndef test_render_xref(function_renderer: AutoFunctionRenderer):\n    function_renderer.objects[\"A\"] = make_class()\n    assert (\n        function_renderer.render_type([TypeXRefInternal(name=\"A\", path=[\"a.\", \"A\"])])\n        == \":js:class:`A`\"\n    )\n    function_renderer.objects[\"A\"] = make_type_alias()\n    assert (\n        function_renderer.render_type([TypeXRefInternal(name=\"A\", path=[\"a.\", \"A\"])])\n        == \":js:typealias:`A`\"\n    )\n    function_renderer.objects[\"A\"] = make_interface()\n    assert (\n        function_renderer.render_type([TypeXRefInternal(name=\"A\", path=[\"a.\", \"A\"])])\n        == \":js:interface:`A`\"\n    )\n    assert (\n        function_renderer.render_type(\n            [TypeXRefInternal(name=\"A\", path=[\"a.\", \"A\"]), \"[]\"]\n        )\n        == r\":js:interface:`A`\\ []\"\n    )\n    xref_external = TypeXRefExternal(\"A\", \"blah\", \"a.ts\", \"a.A\")\n    assert function_renderer.render_type([xref_external]) == \"A\"\n    res = []\n\n    def xref_render(config, val):\n        res.append([config, val])\n        kind = None\n        if isinstance(val, TypeXRefInternal):\n            kind = val.kind\n\n        return f\"{val.package}::{val.name}::{kind}\"\n\n    function_renderer._set_type_xref_formatter(xref_render)\n    assert function_renderer.render_type([xref_external]) == \"blah::A::None\"\n    assert res[0][0] == function_renderer._app.config\n    assert res[0][1] == xref_external\n\n\ndef test_func_render_param_type(function_render):\n    assert function_render(\n        description=\"this is a description\",\n        params=[Param(\"a\", description=\"a description\", type=\"xxx\")],\n    ) == dedent(\n        \"\"\"\\\n        .. js:function:: blah(a)\n\n           this is a description\n\n           :param a: a description\n           :type a: **xxx**\n        \"\"\"\n    )\n    assert function_render(\n        objects={\"A\": make_type_alias()},\n        params=[\n            Param(\n                \"a\",\n                description=\"a description\",\n                type=[TypeXRefInternal(name=\"A\", path=[\"a.\", \"A\"])],\n            )\n        ],\n    ) == dedent(\n        \"\"\"\\\n        .. js:function:: blah(a)\n\n           :param a: a description\n           :type a: :js:typealias:`A`\n        \"\"\"\n    )\n\n\ndef test_func_render_param_options(function_render):\n    assert (\n        function_render(\n            params=[\n                Param(\n                    \"a\",\n                    has_default=True,\n                    default=\"5\",\n                )\n            ],\n        )\n        == \".. js:function:: blah(a=5)\\n\"\n    )\n    assert function_render(\n        params=[\n            Param(\n                \"a\",\n                is_variadic=True,\n            )\n        ],\n    ) == dedent(\".. js:function:: blah(...a)\\n\")\n\n\ndef test_func_render_param_exceptions(function_render):\n    assert function_render(\n        description=\"this is a description\", exceptions=[Exc(\"TypeError\", \"\")]\n    ) == dedent(\n        \"\"\"\\\n        .. js:function:: blah()\n\n           this is a description\n\n           :throws TypeError:\n        \"\"\"\n    )\n\n\ndef test_func_render_callouts(function_render):\n    assert function_render(deprecated=True) == DEFAULT_RESULT + setindent(\n        \"\"\"\n        .. note::\n\n           Deprecated.\n        \"\"\",\n    )\n    assert function_render(deprecated=\"v0.24\") == DEFAULT_RESULT + setindent(\n        \"\"\"\n        .. note::\n\n           Deprecated: v0.24\n        \"\"\",\n    )\n    assert function_render(see_alsos=[\"see\", \"this too\"]) == DEFAULT_RESULT + setindent(\n        \"\"\"\n        .. seealso::\n\n           - :any:`see`\n           - :any:`this too`\n        \"\"\",\n    )\n\n\ndef test_all(function_render):\n    assert function_render(\n        description=\"description\",\n        params=[Param(\"a\", \"xx\")],\n        deprecated=True,\n        exceptions=[Exc(\"TypeError\", \"\")],\n        examples=[\"ex1\"],\n        see_alsos=[\"see\"],\n    ) == dedent(\n        \"\"\"\\\n        .. js:function:: blah(a)\n\n           .. note::\n\n              Deprecated.\n\n           description\n\n           :param a: xx\n           :throws TypeError:\n\n           .. admonition:: Example\n\n              ex1\n\n           .. seealso::\n\n              - :any:`see`\n       \"\"\"\n    )\n\n\ndef test_examples(function_render):\n    assert function_render(examples=[\"ex1\", \"ex2\"]) == DEFAULT_RESULT + setindent(\n        \"\"\"\n        .. admonition:: Example\n\n           ex1\n\n        .. admonition:: Example\n\n           ex2\n        \"\"\",\n    )\n\n    assert function_render(\n        examples=[[DescriptionText(text=\"This is another example.\\n\")]]\n    ) == DEFAULT_RESULT + setindent(\n        \"\"\"\n           .. admonition:: Example\n\n              This is another example.\n        \"\"\"\n    )\n\n    assert function_render(\n        examples=[\n            [DescriptionCode(code=\"```ts\\nThis is an example.\\n```\")],\n            [\n                DescriptionText(text=\"This is another example.\\n\"),\n                DescriptionCode(code=\"```py\\nSomething python\\n```\"),\n            ],\n        ]\n    ) == DEFAULT_RESULT + setindent(\n        \"\"\"\n           .. admonition:: Example\n\n              .. code-block:: ts\n\n                  This is an example.\n\n           .. admonition:: Example\n\n              This is another example.\n\n              .. code-block:: py\n\n                  Something python\n        \"\"\"\n    )\n\n\ndef test_type_alias(type_alias_render):\n    assert type_alias_render() == \".. js:typealias:: blah\\n\"\n    assert type_alias_render(\n        type=\"number\", description=\"my great type alias!\"\n    ) == dedent(\n        \"\"\"\\\n        .. js:typealias:: blah\n\n           .. rst-class:: js attribute type\n\n                  type: **number**\n\n           my great type alias!\n        \"\"\"\n    )\n    assert type_alias_render(\n        type=\"string | T\",\n        type_params=[TypeParam(\"T\", extends=\"number\", description=\"ABC\")],\n        description=\"With a type parameter\",\n    ) == dedent(\n        \"\"\"\\\n        .. js:typealias:: blah<T>\n\n           .. rst-class:: js attribute type\n\n                  type: **string | T**\n\n           With a type parameter\n\n           :typeparam T: ABC (extends **number**)\n        \"\"\"\n    )\n\n\ndef test_auto_module_render(auto_module_render):\n    assert auto_module_render() == \".. js:module:: blah\"\n    assert auto_module_render(\n        functions=[\n            make_function(\n                name=\"f\",\n                description=\"this is a description\",\n                params=[Param(\"a\", description=\"a description\")],\n            ),\n            make_function(name=\"g\"),\n        ],\n        attributes=[make_attribute(name=\"x\", type=\"any\"), make_attribute(name=\"y\")],\n        type_aliases=[\n            make_type_alias(name=\"S\"),\n            make_type_alias(name=\"T\"),\n            # Check that we omit stuff marked with @omitFromAutoModule\n            make_type_alias(name=\"U\", modifier_tags=[\"@omitFromAutoModule\"]),\n        ],\n    ) == dedent(\n        \"\"\"\\\n        .. js:module:: blah\n\n        .. js:typealias:: S\n\n\n        .. js:typealias:: T\n\n\n        .. js:attribute:: x\n\n           .. rst-class:: js attribute type\n\n                  type: **any**\n\n\n        .. js:attribute:: y\n\n\n        .. js:function:: f(a)\n\n           this is a description\n\n           :param a: a description\n\n\n        .. js:function:: g()\n        \"\"\"\n    )\n"
  },
  {
    "path": "tests/test_suffix_tree.py",
    "content": "from pytest import raises\n\nfrom sphinx_js.suffix_tree import SuffixAmbiguous, SuffixNotFound, SuffixTree\n\n\ndef test_things():\n    s = SuffixTree()\n    s.add([\"./\", \"dir/\", \"utils.\", \"max\"], 1)\n    s.add([\"./\", \"dir/\", \"footils.\", \"max\"], 2)\n    s.add([\"./\", \"dir/\", \"footils.\", \"hacks\"], 3)\n\n    assert s.get([\"hacks\"]) == 3\n    assert s.get_with_path([\"footils.\", \"max\"]) == (\n        2,\n        [\"./\", \"dir/\", \"footils.\", \"max\"],\n    )\n    with raises(SuffixNotFound):\n        s.get([\"quacks.\", \"max\"])\n    with raises(SuffixAmbiguous):\n        s.get([\"max\"])\n\n\ndef test_full_path():\n    \"\"\"Looking up a full path should not crash.\"\"\"\n    s = SuffixTree()\n    s.add([\"./\", \"dir/\", \"footils.\", \"jacks\"], 4)\n    assert s.get_with_path([\"./\", \"dir/\", \"footils.\", \"jacks\"]) == (\n        4,\n        [\"./\", \"dir/\", \"footils.\", \"jacks\"],\n    )\n\n\ndef test_terminal_insertion():\n    \"\"\"A non-terminal segment should be able to later be made terminal.\"\"\"\n    s = SuffixTree()\n    s.add([\"a\", \"b\"], 5)\n    s.add([\"b\"], 4)\n    with raises(SuffixAmbiguous):\n        s.get(\"b\")\n\n\ndef test_ambiguous_even_if_full_path():\n    \"\"\"Even full paths should be considered ambiguous if there are paths that\n    have them as suffixes.\"\"\"\n    s = SuffixTree()\n    s.add([\"a\", \"b\"], 5)\n    s.add([\"q\", \"a\", \"b\"], 6)\n    with raises(SuffixAmbiguous):\n        s.get([\"a\", \"b\"])\n\n\ndef test_ambiguous_paths_reported():\n    \"\"\"Make sure SuffixAmbiguous gives a good explanation.\"\"\"\n    s = SuffixTree()\n    s.add([\"q\", \"b\", \"c\"], 5)\n    s.add([\"r\", \"b\", \"c\"], 6)\n    try:\n        s.get([\"b\", \"c\"])\n    except SuffixAmbiguous as exc:\n        assert exc.next_possible_keys == [\"q\", \"r\"]\n        assert not exc.or_ends_here\n\n\ndef test_value_ambiguity():\n    \"\"\"If we're at the point of following single-subtree links, make sure we\n    throw SuffixAmbiguous if we encounter a value and a subtree at a given\n    point (b in this case).\"\"\"\n    s = SuffixTree()\n    s.add([\"a\", \"b\", \"c\"], 5)\n    s.add([\"b\", \"c\"], 6)\n    try:\n        assert s.get([\"c\"])\n    except SuffixAmbiguous as exc:\n        assert exc.next_possible_keys == [\"b\"]\n        assert exc.or_ends_here\n"
  },
  {
    "path": "tests/test_testing.py",
    "content": "from tests.testing import NO_MATCH, dict_where\n\n\ndef test_dict_where():\n    json = {\"hi\": \"there\", \"more\": {\"mister\": \"zangler\", \"and\": \"friends\"}}\n    assert dict_where(json, mister=\"zangler\") == {\"mister\": \"zangler\", \"and\": \"friends\"}\n    assert dict_where(json, mister=\"zangler\", fee=\"foo\") == NO_MATCH\n    assert dict_where(json, hi=\"there\") == json\n"
  },
  {
    "path": "tests/test_typedoc_analysis/source/exports.ts",
    "content": "export interface Blah {\n  a: number;\n  b: string;\n}\n"
  },
  {
    "path": "tests/test_typedoc_analysis/source/nodes.ts",
    "content": "export class Superclass {\n  method() {}\n}\n\nexport interface SuperInterface {}\n\nexport interface Interface extends SuperInterface {}\n\nexport interface InterfaceWithMembers {\n  callableProperty(): void;\n}\n\n/**\n * An empty subclass\n */\nexport abstract class EmptySubclass extends Superclass implements Interface {}\n\nexport abstract class EmptySubclass2\n  extends Promise<number>\n  implements Interface {}\n\nexport const topLevelConst = 3;\n\n/**\n * @param a Some number\n * @param b Some strings\n * @return The best number\n */\nexport function func(a: number = 1, ...b: string[]): number {\n  return 4;\n}\n\nexport class ClassWithProperties {\n  static someStatic: number;\n  someOptional?: number;\n  private somePrivate: number;\n  /**\n   * This is totally normal!\n   */\n  someNormal: number;\n\n  constructor(a: number) {}\n\n  get gettable(): number {\n    return 5;\n  }\n\n  set settable(value: string) {}\n}\n\nexport class Indexable {\n  [id: string]: any; // smoketest\n}\n\n// Test that we don't fail on a reexport\nexport { Blah } from \"./exports\";\n\n/**\n * A super special type alias\n * @typeparam T The whatsit\n */\nexport type TestTypeAlias<T> = 1 | 2 | T;\n"
  },
  {
    "path": "tests/test_typedoc_analysis/source/subdir/pathSegments.ts",
    "content": "/**\n * Function\n */\nexport function foo(): void {\n  /**\n   * An inner function\n   */\n  function inner(): void {}\n}\nfoo.adHocInner = \"innerValue\";\n\n/**\n * Foo class\n */\nexport class Foo {\n  /**\n   * Static member\n   */\n  static staticMember = 8;\n\n  /**\n   * Num instance var\n   */\n  numInstanceVar: number;\n\n  /**\n   * Weird var\n   */\n  \"weird#Var\": number;\n\n  /**\n   * Constructor\n   */\n  constructor(num: number) {\n    this.numInstanceVar = num;\n  }\n\n  /**\n   * Method\n   */\n  someMethod(): void {}\n\n  /**\n   * Static method\n   */\n  static staticMethod(): void {}\n\n  /**\n   * Getter\n   */\n  get getter(): number {\n    return 5;\n  }\n\n  /**\n   * Setter\n   */\n  set setter(n: number) {}\n}\n\nexport interface Face {\n  /**\n   * Interface property\n   */\n  moof: string;\n}\n\nexport namespace SomeSpace {\n  /**\n   * Namespaced number\n   */\n  export const spacedNumber = 4;\n}\n"
  },
  {
    "path": "tests/test_typedoc_analysis/source/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es6\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\"\n  }\n}\n"
  },
  {
    "path": "tests/test_typedoc_analysis/source/types.ts",
    "content": "// Basic types: https://www.typescriptlang.org/docs/handbook/basic-types.html\n\nimport { Blah } from \"./exports\";\n\nexport enum Color {\n  Red = 1,\n  Green = 2,\n}\n\nexport let bool: boolean;\nexport let num: number;\nexport let str: string;\nexport let array: number[];\nexport let genericArray: Array<number>;\nexport let tuple: [string, number];\nexport let color: Color;\nexport let unk: unknown;\nexport let whatever: any;\nexport let voidy: void;\nexport let undef: undefined;\nexport let nully: null;\nexport let nev: never;\nexport let obj: object;\nexport let sym: symbol;\n\n// Interfaces (https://www.typescriptlang.org/docs/handbook/interfaces.html)\n\nexport interface Interface {\n  readonly readOnlyNum: number;\n  [someProp: number]: string; // Just a smoketest for now. (IOW, make sure the analysis engine doesn't crash on it.) We'll need more work to handle members with no names.\n}\n\nexport function interfacer(a: Interface) {}\n\nexport interface FunctionInterface {\n  (thing: string, ding: number): boolean; // just a smoketest for now\n}\n\n// Functions. Basic function types are covered by ConvertNodeTests.test_function.\n\nexport function noThis(this: void) {\n  // smoketest\n}\n\n// Make sure multi-signature functions don't crash us:\nexport function overload(x: string[]): number;\nexport function overload(x: number): number;\nexport function overload(x): any {}\n\n// Literal types (https://www.typescriptlang.org/docs/handbook/literal-types.html)\n\nexport type CertainNumbers = 1 | 2 | 4;\nexport let certainNumbers: CertainNumbers = 2;\n\n// Unions and intersections (https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html)\n\nexport let union: number | string | Color = Color.Red;\n\nexport interface FooHaver {\n  foo: string;\n}\n\nexport interface BarHaver {\n  bar: string;\n}\n\nexport let intersection: FooHaver & BarHaver;\n\n// Generics (https://www.typescriptlang.org/docs/handbook/generics.html)\n\nexport function aryIdentity<T>(things: T[]): T[] {\n  console.log(things.length);\n  return things;\n}\n\nexport class GenericNumber<T> {\n  add: (x: T, y: T) => T;\n}\n\n// Generic constraints:\n\nexport interface Lengthwise {\n  length: number;\n}\n\n/**\n * @typeParam T - the identity type\n */\nexport function constrainedIdentity<T extends Lengthwise>(arg: T): T {\n  return arg;\n}\n\n/**\n * @typeParam T - The type of the object\n * @typeParam K - The type of the key\n */\nexport function getProperty<T, K extends keyof T>(obj: T, key: K) {\n  return obj[key];\n}\n\nexport class A {}\n\nexport function create1(c: { new (x: number): A }): A {\n  return new c(7);\n}\n\nexport function create2<T>(c: { new (): T }): T {\n  return new c();\n}\n\n/**\n * @typeParam S - The type we contain\n */\nexport class ParamClass<S extends number[]> {\n  constructor() {}\n}\n\n// Utility types (https://www.typescriptlang.org/docs/handbook/utility-types.html)\n\n// Most type references in here have ResolvedReferences. These test cases are\n// for \"SymbolReference\". See typedoc/src/lib/models/types.ts\n\n// Partial should generate a SymbolReference that we turn into a\n// TypeXRefExternal\nexport let partial: Partial<string>;\n\n// Blah should generate a SymbolReference that we turn into a\n// TypeXRefInternal\nexport let internalSymbolReference: Blah;\n\n// Complex: nested nightmares that show our ability to handle compound typing constructs\n\nexport function objProps(\n  a: { label: string },\n  b: { label: string; [key: number]: string },\n) {}\n\nexport let option: { a: number; b?: string };\n\n/**\n * Code 1 had `single ticks around it`.\n * Code 2 has ``double ticks around it``.\n * Code 3 has a :sphinx:role:`before it`.\n *\n * ```js\n * A JS code pen!\n * ```\n * And some closing words.\n */\nexport function codeInDescription() {}\n\n/**\n * An example with destructured args 1\n *\n * @param options\n * @param options.a - The 'a' string.\n * @param options.b - The 'b' string.\n * @destructure options\n */\nexport function destructureTest({ a, b }: { b: { c: string }; a: string }) {}\n\n/**\n * An example with destructured args 2\n *\n * @param options\n * @destructure options\n */\nexport function destructureTest2({\n  a,\n  b,\n}: {\n  /** The 'b' object. */\n  b: { c: string };\n  /**  The 'a' string. */\n  a: string;\n}) {}\n\n/**\n * An example with no destructured args 3\n *\n * @param options - The options.\n * @param options.a - The 'a' string.\n * @param options.b - The 'b' string.\n */\nexport function destructureTest3({ a, b }: { a: string; b: { c: string } }) {}\n\n/**\n * A test for should_destructure_arg 4\n */\nexport function destructureTest4(destructureThisPlease: {\n  /**  The 'a' string. */\n  a: string;\n}) {}\n\n/**\n * An example with a function as argument\n *\n * @param a - A number reducer.\n */\nexport function funcArg(a: (b: number, c: number) => number) {}\n\nexport function namedTupleArg(namedTuple: [key: string, value: any]) {}\n\nexport let queryType: typeof A;\nexport let typeOperatorType: keyof A;\n\ntype PrivateTypeAlias1 = { a: number; b: string };\n\n// Should expand the private type alias\nexport let typeIsPrivateTypeAlias1: PrivateTypeAlias1;\n\n/** @private */\nexport type PrivateTypeAlias2 = { a: number; b: string };\n\n// Should expand the private type alias\nexport let typeIsPrivateTypeAlias2: PrivateTypeAlias2;\n\n/**\n * Some comment\n * @hidetype\n */\nexport let hiddenType: number;\n\nexport class HasHiddenTypeMember {\n  /**\n   * Some comment\n   * @hidetype\n   */\n  hasHiddenType: number;\n}\n\nexport let restType: [...number[]];\n\nexport let indexedAccessType: FunctionInterface[\"length\"];\n\nexport type ConditionalType<T> = T extends A ? 1 : 2;\n\nexport type InferredType<T> = T extends Promise<infer S> ? S : T;\n\nexport type keys = \"A\" | \"B\" | \"C\";\n\nexport type MappedType1 = { [property in keys]: number };\nexport type MappedType2 = { -readonly [property in keys]?: number };\nexport type MappedType3 = { readonly [property in keys]-?: number };\n\nexport type TemplateLiteral = `${number}: ${string}`;\n\nexport type OptionalType = [number?];\n\n/**\n * @hidetype\n * @omitFromAutoModule\n * @destructure a.b\n * @summaryLink :role:`target`\n */\nexport type CustomTags = {};\n"
  },
  {
    "path": "tests/test_typedoc_analysis/test_typedoc_analysis.py",
    "content": "from copy import copy, deepcopy\n\nimport pytest\n\nfrom sphinx_js.ir import (\n    Attribute,\n    Class,\n    Description,\n    DescriptionCode,\n    DescriptionText,\n    Function,\n    Interface,\n    Param,\n    Pathname,\n    Return,\n    Type,\n    TypeAlias,\n    TypeParam,\n    TypeXRef,\n    TypeXRefExternal,\n    TypeXRefInternal,\n    TypeXRefIntrinsic,\n)\nfrom sphinx_js.renderers import AutoClassRenderer, AutoFunctionRenderer\nfrom tests.testing import NO_MATCH, TypeDocAnalyzerTestCase, TypeDocTestCase, dict_where\n\n\ndef join_type(t: Type) -> str:\n    if not t:\n        return \"\"\n    if isinstance(t, str):\n        return t\n    return \"\".join(e.name if isinstance(e, TypeXRef) else e for e in t)\n\n\ndef join_description(t: Description) -> str:\n    if not t:\n        return \"\"\n    if isinstance(t, str):\n        return t\n    return \"\".join(e.code if isinstance(e, DescriptionCode) else e.text for e in t)\n\n\nclass TestPathSegments(TypeDocTestCase):\n    \"\"\"Make sure ``make_path_segments() `` works on all its manifold cases.\"\"\"\n\n    files = [\"subdir/pathSegments.ts\"]\n\n    def commented_object(self, comment, **kwargs):\n        \"\"\"Return the object from ``json`` having the given comment short-text.\"\"\"\n        comment = [DescriptionText(text=comment)]\n        return dict_where(self.json, description=comment, **kwargs)\n\n    def commented_object_path(self, comment, **kwargs):\n        \"\"\"Return the path segments of the object with the given comment.\"\"\"\n        obj = self.commented_object(comment, **kwargs)\n        if obj is NO_MATCH:\n            raise RuntimeError(f'No object found with the comment \"{comment}\".')\n        return obj.path.segments  # type:ignore[attr-defined]\n\n    def test_class(self):\n        assert self.commented_object_path(\"Foo class\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"Foo\",\n        ]\n\n    def test_instance_property(self):\n        assert self.commented_object_path(\"Num instance var\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"Foo#\",\n            \"numInstanceVar\",\n        ]\n\n    def test_static_property(self):\n        assert self.commented_object_path(\"Static member\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"Foo.\",\n            \"staticMember\",\n        ]\n\n    def test_interface_property(self):\n        assert self.commented_object_path(\"Interface property\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"Face.\",\n            \"moof\",\n        ]\n\n    def test_weird_name(self):\n        \"\"\"Make sure property names that themselves contain delimiter chars\n        like #./~ get their pathnames built correctly.\"\"\"\n        assert self.commented_object_path(\"Weird var\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"Foo#\",\n            \"weird#Var\",\n        ]\n\n    def test_getter(self):\n        assert self.commented_object_path(\"Getter\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"Foo#\",\n            \"getter\",\n        ]\n\n    def test_setter(self):\n        assert self.commented_object_path(\"Setter\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"Foo#\",\n            \"setter\",\n        ]\n\n    def test_method(self):\n        assert self.commented_object_path(\"Method\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"Foo#\",\n            \"someMethod\",\n        ]\n\n    def test_static_method(self):\n        \"\"\"Since ``make_path_segments()`` looks at the inner Call Signature,\n        make sure the flags (which determine staticness) are on the node we\n        expect.\"\"\"\n        assert self.commented_object_path(\"Static method\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"Foo.\",\n            \"staticMethod\",\n        ]\n\n    def test_constructor(self):\n        # Pass the kindString so we're sure to find the signature (which is\n        # what convert_nodes() passes to make_path_segments()) rather than the\n        # constructor itself. They both have the same comments.\n        #\n        # Constructors get a #. They aren't static; they can see ``this``.\n        assert self.commented_object_path(\"Constructor\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"Foo#\",\n            \"constructor\",\n        ]\n\n    def test_function(self):\n        assert self.commented_object_path(\"Function\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"foo\",\n        ]\n\n    @pytest.mark.xfail(\n        reason=\"Test approach doesn't work anymore and broken by typedoc v0.20\"\n    )\n    def test_relative_paths(self):\n        \"\"\"Make sure FS path segments are emitted if ``base_dir`` doesn't\n        directly contain the code.\"\"\"\n        assert self.commented_object_path(\"Function\") == [\n            \"./\",\n            \"test_typedoc_analysis/\",\n            \"source/\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"foo\",\n        ]\n\n    def test_namespaced_var(self):\n        \"\"\"Make sure namespaces get into the path segments.\"\"\"\n        assert self.commented_object_path(\"Namespaced number\") == [\n            \"./\",\n            \"subdir/\",\n            \"pathSegments.\",\n            \"SomeSpace.\",\n            \"spacedNumber\",\n        ]\n\n\nclass TestConvertNode(TypeDocAnalyzerTestCase):\n    \"\"\"Test all the branches of ``convert_node()`` by analyzing every kind of\n    TypeDoc JSON object.\"\"\"\n\n    files = [\"nodes.ts\", \"exports.ts\"]\n\n    def test_class1(self):\n        \"\"\"Test that superclasses, implemented interfaces, abstractness, and\n        nonexistent constructors, members, and top-level attrs are surfaced.\"\"\"\n        # Make sure is_abstract is sometimes false:\n        super = self.analyzer.get_object([\"Superclass\"])\n        assert isinstance(super, Class)\n        assert not super.is_abstract\n\n        # There should be a single member representing method():\n        (method,) = super.members\n        assert isinstance(method, Function)\n        assert method.name == \"method\"\n\n        # Class-specific attrs:\n        subclass = self.analyzer.get_object([\"EmptySubclass\"])\n        assert isinstance(subclass, Class)\n        assert subclass.constructor_ is None\n        assert subclass.is_abstract\n        assert subclass.interfaces == [\n            [TypeXRefInternal(\"Interface\", [\"./\", \"nodes.\", \"Interface\"])]\n        ]\n\n        subclass2 = self.analyzer.get_object([\"EmptySubclass2\"])\n        assert isinstance(subclass2, Class)\n        assert join_type(subclass2.supers[0]) == \"Promise<number>\"\n\n        # _MembersAndSupers attrs:\n        assert subclass.supers == [\n            [TypeXRefInternal(\"Superclass\", [\"./\", \"nodes.\", \"Superclass\"])]\n        ]\n        assert subclass.members == []\n\n        # TopLevel attrs. This should cover them for other kinds of objs as\n        # well (if node structures are the same across object kinds), since we\n        # have the filling of them factored out.\n        assert subclass.name == \"EmptySubclass\"\n        assert subclass.path == Pathname([\"./\", \"nodes.\", \"EmptySubclass\"])\n        assert subclass.description == [DescriptionText(\"An empty subclass\")]\n        assert subclass.deprecated is False\n        assert subclass.examples == []\n        assert subclass.see_alsos == []\n        assert subclass.properties == []\n        assert subclass.exported_from == Pathname([\"./\", \"nodes\"])\n\n    def test_interface(self):\n        \"\"\"Test that interfaces get indexed and have their supers exposed.\n\n        Members and top-level properties should be covered in test_class()\n        assuming node structure is the same as for classes.\n\n        \"\"\"\n        interface = self.analyzer.get_object([\"Interface\"])\n        assert isinstance(interface, Interface)\n        assert interface.supers == [\n            [TypeXRefInternal(\"SuperInterface\", [\"./\", \"nodes.\", \"SuperInterface\"])]\n        ]\n\n    def test_interface_function_member(self):\n        \"\"\"Make sure function-like properties are understood.\"\"\"\n        obj = self.analyzer.get_object([\"InterfaceWithMembers\"])\n        assert isinstance(obj, Interface)\n        prop = obj.members[0]\n        assert isinstance(prop, Function)\n        assert prop.name == \"callableProperty\"\n\n    def test_variable(self):\n        \"\"\"Make sure top-level consts and vars are found.\"\"\"\n        const = self.analyzer.get_object([\"topLevelConst\"])\n        assert isinstance(const, Attribute)\n        assert const.type == [\"3\"]\n\n    def test_function(self):\n        \"\"\"Make sure Functions, Params, and Returns are built properly for\n        top-level functions.\n\n        This covers a few simple function typing cases as well.\n\n        \"\"\"\n        func = self.analyzer.get_object([\"func\"])\n        assert isinstance(func, Function)\n        assert func.params == [\n            Param(\n                name=\"a\",\n                description=[DescriptionText(\"Some number\")],\n                has_default=True,\n                is_variadic=False,\n                type=[TypeXRefIntrinsic(\"number\")],\n                default=\"1\",\n            ),\n            Param(\n                name=\"b\",\n                description=[DescriptionText(\"Some strings\")],\n                has_default=False,\n                is_variadic=True,\n                type=[TypeXRefIntrinsic(\"string\"), \"[]\"],\n            ),\n        ]\n        assert func.exceptions == []\n        assert func.returns == [\n            Return(\n                type=[TypeXRefIntrinsic(\"number\")],\n                description=[DescriptionText(\"The best number\")],\n            )\n        ]\n\n    def test_constructor(self):\n        \"\"\"Make sure constructors get attached to classes and analyzed into\n        Functions.\n\n        The rest of their analysis should share a code path with functions.\n\n        \"\"\"\n        cls = self.analyzer.get_object([\"ClassWithProperties\"])\n        assert isinstance(cls, Class)\n        assert isinstance(cls.constructor_, Function)\n\n    def test_properties(self):\n        \"\"\"Make sure properties are hooked onto classes and expose their\n        flags.\"\"\"\n        cls = self.analyzer.get_object([\"ClassWithProperties\"])\n        assert isinstance(cls, Class)\n        # The properties are on the class and are Attributes:\n        assert (\n            len(\n                [\n                    m\n                    for m in cls.members\n                    if isinstance(m, Attribute)\n                    and m.name\n                    in [\"someStatic\", \"someOptional\", \"somePrivate\", \"someNormal\"]\n                ]\n            )\n            == 4\n        )\n\n        # The unique things about properties (over and above Variables) are set\n        # right:\n        def get_prop(delim: str, val: str) -> Attribute:\n            res = self.analyzer.get_object([\"ClassWithProperties\" + delim, val])\n            assert isinstance(res, Attribute)\n            return res\n\n        assert get_prop(\".\", \"someStatic\").is_static\n        assert get_prop(\"#\", \"someOptional\").is_optional\n        assert get_prop(\"#\", \"somePrivate\").is_private\n        normal_property = get_prop(\"#\", \"someNormal\")\n        assert (\n            not normal_property.is_optional\n            and not normal_property.is_static\n            and not normal_property.is_abstract\n            and not normal_property.is_private\n        )\n\n    def test_getter(self):\n        \"\"\"Test that we represent getters as Attributes and find their return\n        types.\"\"\"\n        getter = self.analyzer.get_object([\"gettable\"])\n        assert isinstance(getter, Attribute)\n        assert getter.type == [TypeXRefIntrinsic(\"number\")]\n\n    def test_setter(self):\n        \"\"\"Test that we represent setters as Attributes and find the type of\n        their 1 param.\"\"\"\n        setter = self.analyzer.get_object([\"settable\"])\n        assert isinstance(setter, Attribute)\n        assert setter.type == [TypeXRefIntrinsic(\"string\")]\n\n    def test_type_alias(self):\n        alias = self.analyzer.get_object([\"TestTypeAlias\"])\n        assert isinstance(alias, TypeAlias)\n        assert join_description(alias.description) == \"A super special type alias\"\n        assert join_type(alias.type) == \"1 | 2 | T\"\n        assert alias.type_params == [TypeParam(name=\"T\", extends=None, description=[])]\n\n\nclass TestTypeName(TypeDocAnalyzerTestCase):\n    \"\"\"Make sure our rendering of TypeScript types into text works.\"\"\"\n\n    files = [\"types.ts\"]\n\n    def test_basic(self):\n        \"\"\"Test intrinsic types.\"\"\"\n        for obj_name, type_name in [\n            (\"bool\", \"boolean\"),\n            (\"num\", \"number\"),\n            (\"str\", \"string\"),\n            (\"array\", \"number[]\"),\n            (\"genericArray\", \"number[]\"),\n            (\"tuple\", \"[string, number]\"),\n            (\"color\", \"Color\"),\n            (\"unk\", \"unknown\"),\n            (\"whatever\", \"any\"),\n            (\"voidy\", \"void\"),\n            (\"undef\", \"undefined\"),\n            (\"nully\", \"null\"),\n            (\"nev\", \"never\"),\n            (\"obj\", \"object\"),\n            (\"sym\", \"symbol\"),\n        ]:\n            obj = self.analyzer.get_object([obj_name])\n            assert isinstance(obj, Attribute)\n            assert join_type(obj.type) == type_name\n\n    def test_named_interface(self):\n        \"\"\"Make sure interfaces can be referenced by name.\"\"\"\n        obj = self.analyzer.get_object([\"interfacer\"])\n        assert isinstance(obj, Function)\n        assert obj.params[0].type == [\n            TypeXRefInternal(name=\"Interface\", path=[\"./\", \"types.\", \"Interface\"])\n        ]\n\n    def test_interface_readonly_member(self):\n        \"\"\"Make sure the readonly modifier doesn't keep us from computing the\n        type of a property.\"\"\"\n        obj = self.analyzer.get_object([\"Interface\"])\n        assert isinstance(obj, Interface)\n        read_only_num = obj.members[0]\n        assert isinstance(read_only_num, Attribute)\n        assert read_only_num.name == \"readOnlyNum\"\n        assert read_only_num.type == [TypeXRefIntrinsic(\"number\")]\n\n    def test_array(self):\n        \"\"\"Make sure array types are rendered correctly.\n\n        As a bonus, make sure we grab the first signature of an overloaded\n        function.\n\n        \"\"\"\n        obj = self.analyzer.get_object([\"overload\"])\n        assert isinstance(obj, Function)\n        assert obj.params[0].type == [TypeXRefIntrinsic(\"string\"), \"[]\"]\n\n    def test_literal_types(self):\n        \"\"\"Make sure a thing of a named literal type has that type name\n        attached.\"\"\"\n        obj = self.analyzer.get_object([\"certainNumbers\"])\n        assert isinstance(obj, Attribute)\n        assert obj.type == [\n            TypeXRefInternal(\n                name=\"CertainNumbers\", path=[\"./\", \"types.\", \"CertainNumbers\"]\n            )\n        ]\n\n    def test_unions(self):\n        \"\"\"Make sure unions get rendered properly.\"\"\"\n        obj = self.analyzer.get_object([\"union\"])\n        assert isinstance(obj, Attribute)\n        assert obj.type == [\n            TypeXRefIntrinsic(\"number\"),\n            \" | \",\n            TypeXRefIntrinsic(\"string\"),\n            \" | \",\n            TypeXRefInternal(name=\"Color\", path=[\"./\", \"types.\", \"Color\"]),\n        ]\n\n    def test_intersection(self):\n        obj = self.analyzer.get_object([\"intersection\"])\n        assert isinstance(obj, Attribute)\n        assert obj.type == [\n            TypeXRefInternal(name=\"FooHaver\", path=[\"./\", \"types.\", \"FooHaver\"]),\n            \" & \",\n            TypeXRefInternal(name=\"BarHaver\", path=[\"./\", \"types.\", \"BarHaver\"]),\n        ]\n\n    def test_generic_function(self):\n        \"\"\"Make sure type params appear in args and return types.\"\"\"\n        obj = self.analyzer.get_object([\"aryIdentity\"])\n        assert isinstance(obj, Function)\n        T = [\"T\", \"[]\"]\n        assert obj.params[0].type == T\n        assert obj.returns[0].type == T\n\n    def test_generic_member(self):\n        \"\"\"Make sure members of a class have their type params taken into\n        account.\"\"\"\n        obj = self.analyzer.get_object([\"add\"])\n        assert isinstance(obj, Function)\n        assert obj.name == \"add\"\n        assert len(obj.params) == 2\n        T = [\"T\"]\n        assert obj.params[0].type == T\n        assert obj.params[1].type == T\n        assert obj.returns[0].type == T\n\n    def test_constrained_by_interface(self):\n        \"\"\"Make sure ``extends SomeInterface`` constraints are rendered.\"\"\"\n        obj = self.analyzer.get_object([\"constrainedIdentity\"])\n        assert isinstance(obj, Function)\n        T = [\"T\"]\n        assert obj.params[0].type == T\n        assert obj.returns[0].type == T\n        assert obj.type_params[0] == TypeParam(\n            name=\"T\",\n            extends=[\n                TypeXRefInternal(name=\"Lengthwise\", path=[\"./\", \"types.\", \"Lengthwise\"])\n            ],\n            description=[DescriptionText(\"the identity type\")],\n        )\n\n    def test_constrained_by_key(self):\n        \"\"\"Make sure ``extends keyof SomeObject`` constraints are rendered.\"\"\"\n        obj = self.analyzer.get_object([\"getProperty\"])\n        assert isinstance(obj, Function)\n        assert obj.params[0].name == \"obj\"\n        assert join_type(obj.params[0].type) == \"T\"\n        assert join_type(obj.params[1].type) == \"K\"\n        # TODO?\n        # assert obj.returns[0].type == \"<TODO: not implemented>\"\n        assert obj.type_params[0] == TypeParam(\n            name=\"T\",\n            extends=None,\n            description=[DescriptionText(\"The type of the object\")],\n        )\n        tp = copy(obj.type_params[1])\n        tp.extends = join_type(tp.extends)\n        assert tp == TypeParam(\n            name=\"K\",\n            extends=\"string | number | symbol\",\n            description=[DescriptionText(\"The type of the key\")],\n        )\n\n        # TODO: this part maybe belongs in a unit test for the renderer or something\n        a = AutoFunctionRenderer.__new__(AutoFunctionRenderer)\n        a._add_span = False\n        a._set_type_xref_formatter(None)\n        a._explicit_formal_params = None  # type:ignore[attr-defined]\n        a._content = []\n        rst = a.rst([obj.name], obj)\n        rst = rst.replace(\"\\\\\", \"\").replace(\"  \", \" \")\n        assert \":typeparam T: The type of the object\" in rst\n        assert (\n            \":typeparam K: The type of the key (extends string | number | symbol)\"\n            in rst\n        )\n\n    def test_class_constrained(self):\n        # TODO: this may belong somewhere else\n        obj = self.analyzer.get_object([\"ParamClass\"])\n        assert isinstance(obj, Class)\n        tp = copy(obj.type_params[0])\n        tp.extends = join_type(tp.extends)\n        assert tp == TypeParam(\n            name=\"S\",\n            extends=\"number[]\",\n            description=[DescriptionText(\"The type we contain\")],\n        )\n        a = AutoClassRenderer.__new__(AutoClassRenderer)\n        a._set_type_xref_formatter(None)\n        a._explicit_formal_params = None  # type:ignore[attr-defined]\n        a._add_span = False\n        a._content = []\n        a._options = {}\n        rst = a.rst([obj.name], obj)\n        rst = rst.replace(\"\\\\ \", \"\").replace(\"\\\\\", \"\").replace(\"  \", \" \")\n        assert \":typeparam S: The type we contain (extends number[])\" in rst\n\n    def test_constrained_by_constructor(self):\n        \"\"\"Make sure ``new ()`` expressions and, more generally, per-property\n        constraints are rendered properly.\"\"\"\n        obj = self.analyzer.get_object([\"create1\"])\n        assert isinstance(obj, Function)\n        assert join_type(obj.params[0].type) == \"{new (x: number) => A}\"\n        obj = self.analyzer.get_object([\"create2\"])\n        assert isinstance(obj, Function)\n        assert join_type(obj.params[0].type) == \"{new () => T}\"\n\n    def test_utility_types(self):\n        \"\"\"Test that a representative one of TS's utility types renders.\n\n        Partial should generate a SymbolReference that we turn into a\n        TypeXRefExternal\n        \"\"\"\n        obj = self.analyzer.get_object([\"partial\"])\n        assert isinstance(obj, Attribute)\n        t = deepcopy(obj.type)\n        assert t\n        s = t[0]\n        assert isinstance(s, TypeXRefExternal)\n        s.sourcefilename = \"xxx\"\n        assert t == [\n            TypeXRefExternal(\"Partial\", \"typescript\", \"xxx\", \"Partial\"),\n            \"<\",\n            TypeXRefIntrinsic(\"string\"),\n            \">\",\n        ]\n\n    def test_internal_symbol_reference(self):\n        \"\"\"\n        Blah should generate a SymbolReference that we turn into a\n        TypeXRefInternal\n        \"\"\"\n        obj = self.analyzer.get_object([\"internalSymbolReference\"])\n        assert isinstance(obj, Attribute)\n        assert obj.type == [\n            TypeXRefInternal(name=\"Blah\", path=[\"./\", \"exports\"], type=\"internal\")\n        ]\n\n    def test_constrained_by_property(self):\n        obj = self.analyzer.get_object([\"objProps\"])\n        assert isinstance(obj, Function)\n        assert obj.params[0].type == [\n            \"{ \",\n            \"label\",\n            \": \",\n            TypeXRefIntrinsic(\"string\"),\n            \"; \",\n            \"}\",\n        ]\n        assert (\n            join_type(obj.params[1].type) == \"{ [key: number]: string; label: string; }\"\n        )\n\n    def test_optional_property(self):\n        \"\"\"Make sure optional properties render properly.\"\"\"\n        obj = self.analyzer.get_object([\"option\"])\n        assert isinstance(obj, Attribute)\n        assert join_type(obj.type) == \"{ a: number; b?: string; }\"\n\n    def test_code_in_description(self):\n        obj = self.analyzer.get_object([\"codeInDescription\"])\n        assert obj.description == [\n            DescriptionText(text=\"Code 1 had \"),\n            DescriptionCode(code=\"`single ticks around it`\"),\n            DescriptionText(text=\".\\nCode 2 has \"),\n            DescriptionCode(code=\"``double ticks around it``\"),\n            DescriptionText(text=\".\\nCode 3 has a :sphinx:role:\"),\n            DescriptionCode(code=\"`before it`\"),\n            DescriptionText(text=\".\\n\\n\"),\n            DescriptionCode(code=\"```js\\nA JS code pen!\\n```\"),\n            DescriptionText(text=\"\\nAnd some closing words.\"),\n        ]\n\n    def test_destructured(self):\n        obj = self.analyzer.get_object([\"destructureTest\"])\n        assert isinstance(obj, Function)\n        # Parameters should be sorted by source position in the type annotation not by name.\n        assert obj.params[0].name == \"options.b\"\n        assert join_type(obj.params[0].type) == \"{ c: string; }\"\n        assert obj.params[0].description == [DescriptionText(text=\"The 'b' string.\")]\n        assert obj.params[1].name == \"options.a\"\n        assert join_type(obj.params[1].type) == \"string\"\n        assert obj.params[1].description == [DescriptionText(text=\"The 'a' string.\")]\n\n        obj = self.analyzer.get_object([\"destructureTest2\"])\n        assert isinstance(obj, Function)\n        assert obj.params[0].name == \"options.b\"\n        assert join_type(obj.params[0].type) == \"{ c: string; }\"\n        assert obj.params[0].description == [DescriptionText(text=\"The 'b' object.\")]\n\n        assert obj.params[1].name == \"options.a\"\n        assert join_type(obj.params[1].type) == \"string\"\n        assert obj.params[1].description == [DescriptionText(text=\"The 'a' string.\")]\n\n        obj = self.analyzer.get_object([\"destructureTest3\"])\n        assert isinstance(obj, Function)\n        assert obj.params[0].name == \"options\"\n        assert join_type(obj.params[0].type) == \"{ a: string; b: { c: string; }; }\"\n\n        obj = self.analyzer.get_object([\"destructureTest4\"])\n        assert isinstance(obj, Function)\n        assert obj.params[0].name == \"destructureThisPlease.a\"\n        assert join_type(obj.params[0].type) == \"string\"\n        assert obj.params[0].description == [DescriptionText(text=\"The 'a' string.\")]\n\n    def test_funcarg(self):\n        obj = self.analyzer.get_object([\"funcArg\"])\n        assert isinstance(obj, Function)\n        assert obj.params[0].name == \"a\"\n        assert join_type(obj.params[0].type) == \"(b: number, c: number) => number\"\n\n    def test_namedtuplearg(self):\n        obj = self.analyzer.get_object([\"namedTupleArg\"])\n        assert isinstance(obj, Function)\n        assert obj.params[0].name == \"namedTuple\"\n        assert join_type(obj.params[0].type) == \"[key: string, value: any]\"\n\n    def test_query(self):\n        obj = self.analyzer.get_object([\"queryType\"])\n        assert isinstance(obj, Attribute)\n        assert join_type(obj.type) == \"typeof A\"\n\n    def test_type_operator(self):\n        obj = self.analyzer.get_object([\"typeOperatorType\"])\n        assert isinstance(obj, Attribute)\n        assert join_type(obj.type) == \"keyof A\"\n\n    def test_private_type_alias1(self):\n        obj = self.analyzer.get_object([\"typeIsPrivateTypeAlias1\"])\n        assert isinstance(obj, Attribute)\n        assert join_type(obj.type) == \"{ a: number; b: string; }\"\n\n    def test_private_type_alias2(self):\n        obj = self.analyzer.get_object([\"typeIsPrivateTypeAlias2\"])\n        assert isinstance(obj, Attribute)\n        assert join_type(obj.type) == \"{ a: number; b: string; }\"\n\n    def test_hidden_type_top_level(self):\n        obj = self.analyzer.get_object([\"hiddenType\"])\n        assert obj.modifier_tags == [\"@hidetype\"]\n        assert isinstance(obj, Attribute)\n        assert obj.type == []\n\n    def test_hidden_type_member(self):\n        obj = self.analyzer.get_object([\"HasHiddenTypeMember\"])\n        assert isinstance(obj, Class)\n        assert obj.members\n        member = obj.members[0]\n        assert isinstance(member, Attribute)\n        assert member.type == []\n\n    def test_rest_type(self):\n        obj = self.analyzer.get_object([\"restType\"])\n        assert isinstance(obj, Attribute)\n        assert join_type(obj.type) == \"[...number[]]\"\n\n    def test_indexed_access_type(self):\n        obj = self.analyzer.get_object([\"indexedAccessType\"])\n        assert isinstance(obj, Attribute)\n        assert join_type(obj.type) == 'FunctionInterface[\"length\"]'\n\n    def test_conditional_type(self):\n        obj = self.analyzer.get_object([\"ConditionalType\"])\n        assert isinstance(obj, TypeAlias)\n        assert join_type(obj.type) == \"T extends A ? 1 : 2\"\n\n    def test_inferred_type(self):\n        obj = self.analyzer.get_object([\"InferredType\"])\n        assert isinstance(obj, TypeAlias)\n        assert join_type(obj.type) == \"T extends Promise<infer S> ? S : T\"\n\n    def test_mapped_type(self):\n        obj = self.analyzer.get_object([\"MappedType1\"])\n        assert isinstance(obj, TypeAlias)\n        assert join_type(obj.type) == \"{ [property in keys]: number }\"\n        obj = self.analyzer.get_object([\"MappedType2\"])\n        assert isinstance(obj, TypeAlias)\n        assert join_type(obj.type) == \"{ -readonly [property in keys]?: number }\"\n        obj = self.analyzer.get_object([\"MappedType3\"])\n        assert isinstance(obj, TypeAlias)\n        assert join_type(obj.type) == \"{ readonly [property in keys]-?: number }\"\n\n    def test_template_literal(self):\n        obj = self.analyzer.get_object([\"TemplateLiteral\"])\n        assert isinstance(obj, TypeAlias)\n        assert join_type(obj.type) == \"`${number}: ${string}`\"\n\n    def test_custom_tags(self):\n        obj = self.analyzer.get_object([\"CustomTags\"])\n        assert isinstance(obj, TypeAlias)\n        assert \"@hidetype\" in obj.modifier_tags\n        assert \"@omitFromAutoModule\" in obj.modifier_tags\n        assert [join_description(d) for d in obj.block_tags[\"summaryLink\"]] == [\n            \":role:`target`\"\n        ]\n        assert [join_description(d) for d in obj.block_tags[\"destructure\"]] == [\"a.b\"]\n"
  },
  {
    "path": "tests/testing.py",
    "content": "import sys\nfrom inspect import getmembers\nfrom os.path import dirname, join\nfrom shutil import rmtree\n\nfrom sphinx.cmd.build import main as sphinx_main\n\nfrom sphinx_js.jsdoc import Analyzer as JsAnalyzer\nfrom sphinx_js.jsdoc import jsdoc_output\nfrom sphinx_js.typedoc import Analyzer as TsAnalyzer\nfrom sphinx_js.typedoc import typedoc_output\n\n\nclass ThisDirTestCase:\n    \"\"\"A TestCase that knows how to find the directory the subclass is defined\n    in\"\"\"\n\n    @classmethod\n    def this_dir(cls):\n        \"\"\"Return the path to the dir containing the testcase class.\"\"\"\n        # nose does some amazing magic that makes this work even if there are\n        # multiple test modules with the same name:\n        return dirname(sys.modules[cls.__module__].__file__)\n\n\nclass SphinxBuildTestCase(ThisDirTestCase):\n    \"\"\"Base class for tests which require a Sphinx tree to be built and then\n    deleted afterward\n\n    \"\"\"\n\n    builder = \"text\"\n\n    @classmethod\n    def setup_class(cls):\n        \"\"\"Run Sphinx against the dir adjacent to the testcase.\"\"\"\n        cls.docs_dir = join(cls.this_dir(), \"source\", \"docs\")\n        # -v for better tracebacks:\n        if sphinx_main(\n            [cls.docs_dir, \"-b\", cls.builder, \"-v\", \"-E\", join(cls.docs_dir, \"_build\")]\n        ):\n            raise RuntimeError(\"Sphinx build exploded.\")\n\n    @classmethod\n    def teardown_class(cls):\n        rmtree(join(cls.docs_dir, \"_build\"))\n\n    def _file_contents(self, filename):\n        extension = \"txt\" if self.builder == \"text\" else \"html\"\n        with open(\n            join(self.docs_dir, \"_build\", f\"{filename}.{extension}\"),\n            encoding=\"utf8\",\n        ) as file:\n            return file.read()\n\n    def _file_contents_eq(self, filename, expected_contents):\n        __tracebackhide__ = True\n        contents = self._file_contents(filename)\n        # Fix a difference between sphinx v6 and v7\n        contents = contents.replace(\" --\\n\", \"\\n\")\n        assert contents == expected_contents\n\n\nclass JsDocTestCase(ThisDirTestCase):\n    \"\"\"Base class for tests which analyze a file using JSDoc\"\"\"\n\n    @classmethod\n    def setup_class(cls):\n        \"\"\"Run the JS analyzer over the JSDoc output.\"\"\"\n        source_dir = join(cls.this_dir(), \"source\")\n        output = jsdoc_output(\n            None, [join(source_dir, cls.file)], source_dir, source_dir\n        )\n        cls.analyzer = JsAnalyzer(output, source_dir)\n\n\nclass TypeDocTestCase(ThisDirTestCase):\n    \"\"\"Base class for tests which imbibe TypeDoc's output\"\"\"\n\n    @classmethod\n    def setup_class(cls):\n        \"\"\"Run the TS analyzer over the TypeDoc output.\"\"\"\n        cls._source_dir = join(cls.this_dir(), \"source\")\n        from pathlib import Path\n\n        config_file = Path(__file__).parent / \"sphinxJsConfig.ts\"\n\n        [cls.json, cls.extra_data] = typedoc_output(\n            abs_source_paths=[join(cls._source_dir, file) for file in cls.files],\n            base_dir=cls._source_dir,\n            ts_sphinx_js_config=str(config_file),\n            typedoc_config_path=None,\n            tsconfig_path=\"tsconfig.json\",\n            sphinx_conf_dir=cls._source_dir,\n        )\n\n\nclass TypeDocAnalyzerTestCase(TypeDocTestCase):\n    \"\"\"Base class for tests which analyze a file using TypeDoc\"\"\"\n\n    @classmethod\n    def setup_class(cls):\n        \"\"\"Run the TS analyzer over the TypeDoc output.\"\"\"\n        super().setup_class()\n\n        cls.analyzer = TsAnalyzer(cls.json, cls.extra_data, cls._source_dir)\n\n\nNO_MATCH = object()\n\n\ndef dict_where(json, already_seen=None, **kwargs):\n    \"\"\"Return the first object in the given data structure with properties\n    equal to the  ones given by ``kwargs``.\n\n    For example::\n\n        >>> dict_where({'hi': 'there', {'mister': 'zangler', 'and': 'friends'}},\n                       mister=zangler)\n        {'mister': 'zangler', 'and': 'friends'}\n\n    So far, only dicts and lists are supported. Other data structures won't be\n    recursed into. Cycles are avoided.\n\n    \"\"\"\n\n    def matches_properties(json, **kwargs):\n        \"\"\"Return the given JSON object iff all the properties and values given\n        by ``kwargs`` are in it. Else, return NO_MATCH.\"\"\"\n        for k, v in kwargs.items():\n            if json.get(k, NO_MATCH) != v:\n                return False\n        return True\n\n    if already_seen is None:\n        already_seen = set()\n    already_seen.add(id(json))\n    if isinstance(json, list):\n        for list_item in json:\n            if id(list_item) not in already_seen:\n                match = dict_where(list_item, already_seen, **kwargs)\n                if match is not NO_MATCH:\n                    return match\n    elif isinstance(json, dict):\n        if matches_properties(json, **kwargs):\n            return json\n        for v in json.values():\n            if id(v) not in already_seen:\n                match = dict_where(v, already_seen, **kwargs)\n                if match is not NO_MATCH:\n                    return match\n    elif hasattr(type(json), \"__attrs_attrs__\"):\n        d = dict([k, v] for [k, v] in getmembers(json) if not k.startswith(\"_\"))\n        if matches_properties(d, **kwargs):\n            return json\n        for k, v in d.items():\n            if k.startswith(\"_\"):\n                continue\n            if id(v) not in already_seen:\n                match = dict_where(v, already_seen, **kwargs)\n                if match is not NO_MATCH:\n                    return match\n    else:\n        # We don't know how to match leaf values yet.\n        pass\n    return NO_MATCH\n"
  }
]