diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d6c7ab481..16c72ff2d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -38,7 +38,7 @@ If this is not possible, include at least: ## Environment -- Typedoc version: +- TypeDoc version: - TypeScript version: - Node.js version: - OS: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ccfbb7068..63caaa7d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,10 @@ jobs: - name: Circular dependency check uses: gerrit0/circular-dependency-check@v2.0.2 with: - entry: dist/index.js + entry: | + dist/index.js + dist/lib/models/index.js + dist/browser-utils.js build-release: runs-on: ubuntu-latest name: Node 22 Release diff --git a/.gitignore b/.gitignore index 0ed4251ff..fbbe7596d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,11 +13,12 @@ yarn-error.log /src/test/renderer/testProject/json.json **/node_modules/ !src/test/converter2/behavior/node_modules/ +!src/test/converter2/renderer/node_modules/ /coverage/ /dist/ /docs -/docs-site /docs2 +/docs-* /td*.json typedoc*.tgz diff --git a/.npmrc b/.npmrc index 38a20c41b..ce1fd3424 100644 --- a/.npmrc +++ b/.npmrc @@ -6,3 +6,6 @@ audit = false # While we're on the TS beta, need this flag. legacy-peer-deps = true + +# This breaks some of the scripts in the scripts directory +update-notifier = false diff --git a/.vscode/settings.json b/.vscode/settings.json index f2c994728..fb47a1c4a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ }, "typescript.tsdk": "./node_modules/typescript/lib", + "dprint.path": "./node_modules/dprint/dprint", // Automatically run the formatter when certain files are saved. "[javascript]": { @@ -65,6 +66,7 @@ "Msys", "nodoc", "shiki", + "srcset", "tsbuildinfo", "tsdoc", "typedoc", diff --git a/CHANGELOG.md b/CHANGELOG.md index a5d49fd7a..e769444be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,169 @@ title: Changelog ## Unreleased +## v0.28.14 (2025-10-11) + +### Features + +- Introduced the `preservedTypeAnnotationTags` option to specify tags whose type annotations should + be copied to the output documentation, #3020. + API: Introduced `typeAnnotation` on `CommentTag` +- Added `excludePrivateClassFields` option to hide `#private` members while allowing `private` members, #3017. +- Added support for TypeScript's `@this` tag for JS files which describe `this` parameters, #3026. + +## Bug Fixes + +- Fixed conversion of auto-accessor types on properties with the `accessor` keyword, #3019. +- Improved handling of HTML tags within headers for anchor generation, #3023. +- Improved support for detecting destructured parameters and renaming them to the name used in the doc comment, #3026. + +## v0.28.13 (2025-09-14) + +### Features + +- The `basePath` option now also affects relative link resolution, TypeDoc will also check for + paths relative to the provided base path. If you instead want TypeDoc to only change the rendered + base path for sources, use the `displayBasePath` option, #3009. + +### Bug Fixes + +- Fixed bug introduced in 0.28.8 where TypeDoc could not render docs with some mixin classes, #3007. +- `@inheritDoc` will now correctly overwrite `@remarks` and `@returns` blocks on the target comment, #3012. +- The `externalSymbolLinkMappings` option now works properly on links pointing to inherited/overwritten signatures, #3014. + +## v0.28.12 (2025-09-01) + +### Bug Fixes + +- Variables marked with `@enum` now work for symbols imported from another module, #3003. +- Improved magic introduced with #2999 to work with imported symbols, #3003. +- Fixed relative link resolution to file names containing percent encoded URLs, #3006. +- Linking to the project's README file with a relative link will now behave as expected, #3006. +- Reduced unnecessary HTML element rendering in default theme. + API: `Reflection.hasComment` and `Comment.hasVisibleComponent` now accepts an optional `notRenderedTags` parameter. + +## v0.28.11 (2025-08-25) + +### Features + +- Object properties declared with shorthand property assignment will now use the variable's comment + if they do not have their own comment, #2999. + +### Bug Fixes + +- Fixed link resolution not working correctly in first comment on the file in some cases, #2994. +- Optional methods are now rendered with a trailing `?` in the reflection preview and signature, #2995. +- The `compilerOptions` option now functions properly with non-boolean options, #3000. +- Configuration errors within the `compilerOptions` option are now handled gracefully, #3000. +- Fixed improper casing of "Type Declaration" header, #3002. + +## v0.28.10 (2025-08-10) + +### Bug Fixes + +- Fixed inconsistent anchors on module pages for re-exports, #2990. +- Markdown references which appear to be footnotes will no longer be checked for links, #2991. + +## v0.28.9 (2025-08-01) + +### Features + +- Add support for TypeScript 5.9, #2989. + +### Bug Fixes + +- Fixed bug introduced in 0.28.8 where TypeDoc could not render docs when members inherited from a complex type alias, #2982. +- Fixed automatic discovery of entry points when not running in packages mode, #2988. +- Fixed discovery of package.json file when running with entry points containing a glob, #2985. + +## v0.28.8 (2025-07-28) + +### Features + +- If using JS config files, the `plugin` function can now be given plugin functions to load. +- Permit `-` within tag names to support `typescript-json-schema`'s `@TJS-type` tag, #2972. +- Exposed `Context.createSymbolId` for use by plugins. + +### Bug Fixes + +- Relative links in `` will now be discovered by TypeDoc, #2975. +- Relative links in `` and `` elements will now be discovered by TypeDoc, #2975. +- Improved inherited from/overwrites link discovery to point to parent properties in more cases, #2978 + +### Thanks! + +- @jonathanhefner +- @laymonage + +## v0.28.7 (2025-06-30) + +### Features + +- Introduced the `@sortStrategy` tag to override the `sort` option on a specific reflection, #2965. + +### Bug Fixes + +- Classes and functions exported with `export { type X }` are no longer missing comments, #2970. +- Setting `locale` to an unknown value will now cause TypeDoc to operate in English instead of a debug locale. +- Array options will now report an error if set to a non-array/non-string value. + +## v0.28.6 (2025-06-27) + +### Features + +- TypeDoc now supports resolving relative paths in links to the package directory as belonging to the project, #2961. +- Declarations without comments will now check for comments on their export specifier, #2964. + +### Bug Fixes + +- Attempting to highlight a supported language which is not enabled is now a warning, not an error, #2956. +- Improved compatibility with CommonMark's link parsing, #2959. +- Classes, variables, and functions exported with `export { type X }` are now detected and converted as interfaces/type aliases, #2962. +- Improved warning messaging for links to symbols which were resolved, but the symbols were not included in the documentation, #2967. +- Fixed an issue preventing nested documents from being deserialized from TypeDoc's JSON output or used in packages mode, #2969. + +### Thanks! + +- @yGuy + +## v0.28.5 (2025-05-26) + +### Bug Fixes + +- References to type aliases defined as mapped types will now correctly create a reference to the type alias, #2954. +- `ignoredHighlightLanguages` can now be used to prevent warnings for codeblocks containing languages + which are supported by Shiki but are not loaded, #2956. + +## v0.28.4 (2025-05-04) + +### Features + +- The navigation in the default theme will now attempt to break long names onto multiple lines, #2940. +- Added German (de) localization, #2941. + +### Bug Fixes + +- TypeDoc's default theme now uses the same chevron for all collapsible elements, #2924 + The `chevronSmall` helper is now deprecated and will be removed with v0.29.0. +- Classes/interfaces marked with `@hidden` will no longer appear in the + "Hierarchy" section of the docs. +- TypeDoc now handles wildcard JSDoc types, #2949. + +### Thanks! + +- @blutorange +- @bkeepers + +## v0.28.3 (2025-04-20) + +### Bug Fixes + +- `@inline` now functions when referencing tuple types, #2932. +- `@link` links to the current page are now rendered, #2934. +- `@includeCode` now supports regions in TypeScript files with `.mts` and `.cts` file extensions, #2935. +- Aliased symbols (re-exports) are now resolved before checking if they are excluded/external, #2937. +- Improved error reporting when paths including Windows separators are provided as globs, #2938. + ## v0.28.2 (2025-04-07) ### Features diff --git a/dprint.json b/dprint.json index 25bbe47f3..4faadb249 100644 --- a/dprint.json +++ b/dprint.json @@ -19,9 +19,9 @@ "src/test/converter2/issues/gh2631/crlf.md" ], "plugins": [ - "https://plugins.dprint.dev/typescript-0.94.0.wasm", + "https://plugins.dprint.dev/typescript-0.95.9.wasm", "https://plugins.dprint.dev/json-0.20.0.wasm", - "https://plugins.dprint.dev/markdown-0.18.0.wasm", - "https://plugins.dprint.dev/g-plane/malva-v0.11.2.wasm" + "https://plugins.dprint.dev/markdown-0.19.0.wasm", + "https://plugins.dprint.dev/g-plane/malva-v0.14.1.wasm" ] } diff --git a/eslint.config.mjs b/eslint.config.mjs index 9544f7862..ff73874f2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -53,6 +53,13 @@ const config = { // This is sometimes useful for clarity "@typescript-eslint/no-unnecessary-type-arguments": "off", + // It'd be kind of nice to be able to turn this on, but it doesn't seem to be worth the additional + // pain at this point. Turning it on adds ~300 lint errors, of which manual review found 1 bug, and + // whose suggestions would have introduced 3 bugs that I noticed, and potentially more... and it + // also didn't catch the bug which I originally turned it on for! Ideally, I'd like a version of + // this which ONLY checks for boolean comparisons of type number. + "@typescript-eslint/strict-boolean-expressions": "off", + // We still use `any` fairly frequently... "@typescript-eslint/ban-types": "off", "@typescript-eslint/no-explicit-any": "off", @@ -244,6 +251,12 @@ export default tslint.config( eslint.configs.recommended, ...tslint.configs.strictTypeChecked, config, + { + files: ["src/test/**/*"], + rules: { + "@typescript-eslint/no-deprecated": "off", + }, + }, { ignores: [ "eslint.config.mjs", diff --git a/package.json b/package.json index 4b05dd0af..24524f3d9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "typedoc", "description": "Create api documentation for TypeScript projects.", - "version": "0.28.2", + "version": "0.28.14", "homepage": "https://typedoc.org", "type": "module", "exports": { @@ -31,32 +31,32 @@ "pnpm": ">= 10" }, "dependencies": { - "@gerrit0/mini-shiki": "^3.2.2", + "@gerrit0/mini-shiki": "^3.12.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", - "yaml": "^2.7.1" + "yaml": "^2.8.1" }, "peerDependencies": { - "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x" }, "devDependencies": { - "@eslint/js": "^9.24.0", + "@eslint/js": "^9.34.0", "@types/lunr": "^2.3.7", "@types/markdown-it": "^14.1.2", "@types/mocha": "^10.0.10", "@types/node": "18", "@typestrong/fs-fixture-builder": "github:TypeStrong/fs-fixture-builder#34113409e3a171e68ce5e2b55461ef5c35591cfe", "c8": "^10.1.3", - "dprint": "^0.49.1", - "esbuild": "^0.25.2", - "eslint": "^9.24.0", - "mocha": "^11.1.0", - "puppeteer": "^24.6.0", - "semver": "^7.7.1", - "tsx": "^4.19.3", - "typescript": "5.8.3", - "typescript-eslint": "^8.29.0" + "dprint": "^0.50.1", + "esbuild": "^0.25.9", + "eslint": "^9.34.0", + "mocha": "^11.7.1", + "puppeteer": "^24.17.1", + "semver": "^7.7.2", + "tsx": "^4.20.5", + "typescript": "5.9.2", + "typescript-eslint": "^8.41.0" }, "files": [ "/bin", @@ -73,9 +73,9 @@ "test": "mocha --config .config/mocha.fast.json", "test:cov": "c8 -r lcov mocha --config .config/mocha.fast.json", "doc:c": "node bin/typedoc --tsconfig src/test/converter/tsconfig.json", - "doc:cd": "node --inspect-brk bin/typedoc --tsconfig src/test/converter/tsconfig.json", + "doc:cd": "node --inspect-brk dist/lib/cli.js --tsconfig src/test/converter/tsconfig.json", "doc:c2": "node bin/typedoc --options src/test/converter2 --tsconfig src/test/converter2/tsconfig.json", - "doc:c2d": "node --inspect-brk bin/typedoc --options src/test/converter2 --tsconfig src/test/converter2/tsconfig.json", + "doc:c2d": "node --inspect-brk dist/lib/cli.js --options src/test/converter2 --tsconfig src/test/converter2/tsconfig.json", "example": "cd example && node ../bin/typedoc", "test:full": "c8 -r lcov -r text-summary mocha --config .config/mocha.full.json", "rebuild_specs": "node scripts/rebuild_specs.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ffae878e..b2ad20010 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@gerrit0/mini-shiki': - specifier: ^3.2.2 - version: 3.2.2 + specifier: ^3.12.0 + version: 3.12.0 lunr: specifier: ^2.3.9 version: 2.3.9 @@ -21,12 +21,12 @@ importers: specifier: ^9.0.5 version: 9.0.5 yaml: - specifier: ^2.7.1 - version: 2.7.1 + specifier: ^2.8.1 + version: 2.8.1 devDependencies: '@eslint/js': - specifier: ^9.24.0 - version: 9.24.0 + specifier: ^9.34.0 + version: 9.34.0 '@types/lunr': specifier: ^2.3.7 version: 2.3.7 @@ -38,7 +38,7 @@ importers: version: 10.0.10 '@types/node': specifier: '18' - version: 18.19.76 + version: 18.19.113 '@typestrong/fs-fixture-builder': specifier: github:TypeStrong/fs-fixture-builder#34113409e3a171e68ce5e2b55461ef5c35591cfe version: https://codeload.github.com/TypeStrong/fs-fixture-builder/tar.gz/34113409e3a171e68ce5e2b55461ef5c35591cfe @@ -46,244 +46,250 @@ importers: specifier: ^10.1.3 version: 10.1.3 dprint: - specifier: ^0.49.1 - version: 0.49.1 + specifier: ^0.50.1 + version: 0.50.1 esbuild: - specifier: ^0.25.2 - version: 0.25.2 + specifier: ^0.25.9 + version: 0.25.9 eslint: - specifier: ^9.24.0 - version: 9.24.0 + specifier: ^9.34.0 + version: 9.34.0 mocha: - specifier: ^11.1.0 - version: 11.1.0 + specifier: ^11.7.1 + version: 11.7.1 puppeteer: - specifier: ^24.6.0 - version: 24.6.0(typescript@5.8.3) + specifier: ^24.17.1 + version: 24.17.1(typescript@5.9.2) semver: - specifier: ^7.7.1 - version: 7.7.1 + specifier: ^7.7.2 + version: 7.7.2 tsx: - specifier: ^4.19.3 - version: 4.19.3 + specifier: ^4.20.5 + version: 4.20.5 typescript: - specifier: 5.8.3 - version: 5.8.3 + specifier: 5.9.2 + version: 5.9.2 typescript-eslint: - specifier: ^8.29.0 - version: 8.29.0(eslint@9.24.0)(typescript@5.8.3) + specifier: ^8.41.0 + version: 8.41.0(eslint@9.34.0)(typescript@5.9.2) packages: - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@dprint/darwin-arm64@0.49.1': - resolution: {integrity: sha512-ib6KcJWo/M5RJWXOQKhP664FG1hAvG7nrbkh+j8n+oXdzmbyDdXTP+zW+aM3/sIQUkGaZky1xy1j2VeScMEEHQ==} + '@dprint/darwin-arm64@0.50.1': + resolution: {integrity: sha512-NNKf3dxXn567pd/hpCVLHLbC0dI7s3YvQnUEwjRTOAQVMp6O7/ME+Tg1RPGsDP1IB+Y2fIYSM4qmG02zQrqjAQ==} cpu: [arm64] os: [darwin] - '@dprint/darwin-x64@0.49.1': - resolution: {integrity: sha512-vIVgnYxV7YYa1d6Uyz707RbgB9rwefGPam+rzaueFNPQjdOxPOTQDuMEJDS+Z3BlI00MfeoupIfIUGsXoM4dpQ==} + '@dprint/darwin-x64@0.50.1': + resolution: {integrity: sha512-PcY75U3UC/0CLOxWzE0zZJZ2PxzUM5AX2baYL1ovgDGCfqO1H0hINiyxfx/8ncGgPojWBkLs+zrcFiGnXx7BQg==} cpu: [x64] os: [darwin] - '@dprint/linux-arm64-glibc@0.49.1': - resolution: {integrity: sha512-ZeIh6qMPWLBBifDtU0XadpK36b4WoaTqCOt0rWKfoTjq1RAt78EgqETWp43Dbr6et/HvTgYdoWF0ZNEu2FJFFA==} + '@dprint/linux-arm64-glibc@0.50.1': + resolution: {integrity: sha512-q0TOGy9FsoSKsEQ4sIMKyFweF5M8rW1S5OfwJDNRR2TU2riWByU9TKYIZUzg53iuwYKRypr/kJ5kdbl516afRQ==} cpu: [arm64] os: [linux] - '@dprint/linux-arm64-musl@0.49.1': - resolution: {integrity: sha512-/nuRyx+TykN6MqhlSCRs/t3o1XXlikiwTc9emWdzMeLGllYvJrcht9gRJ1/q1SqwCFhzgnD9H7roxxfji1tc+Q==} + '@dprint/linux-arm64-musl@0.50.1': + resolution: {integrity: sha512-XRtxN2cA9rc06WFzzVPDIZYGGLmUXqpVf3F0XhhHV77ikQLJZ5reF4xBOQ+0HjJ/zy8W/HzuGDAHedWyCrRf9g==} cpu: [arm64] os: [linux] - '@dprint/linux-riscv64-glibc@0.49.1': - resolution: {integrity: sha512-RHBqrnvGO+xW4Oh0QuToBqWtkXMcfjqa1TqbBFF03yopFzZA2oRKX83PhjTWgd/IglaOns0BgmaLJy/JBSxOfQ==} + '@dprint/linux-riscv64-glibc@0.50.1': + resolution: {integrity: sha512-vAk/eYhSjA3LJ/yuYgxkHamiK8+m6YdqVBO/Ka+i16VxyjQyOdcMKBkrLCIqSxgyXd6b8raf9wM59HJbaIpoOg==} cpu: [riscv64] os: [linux] - '@dprint/linux-x64-glibc@0.49.1': - resolution: {integrity: sha512-MjFE894mIQXOKBencuakKyzAI4KcDe/p0Y9lRp9YSw/FneR4QWH9VBH90h8fRxcIlWMArjFFJJAtsBnn5qgxeg==} + '@dprint/linux-x64-glibc@0.50.1': + resolution: {integrity: sha512-EpW5KLekaq4hXmKBWWtfBgZ244S4C+vFmMOd1YaGi8+f0hmPTJzVWLdIgpO2ZwfPQ5iycaVI/JS514PQmXPOvg==} cpu: [x64] os: [linux] - '@dprint/linux-x64-musl@0.49.1': - resolution: {integrity: sha512-CvGBWOksHgrL1uzYqtPFvZz0+E82BzgoCIEHJeuYaveEn37qWZS5jqoCm/vz6BfoivE1dVuyyOT78Begj9KxkQ==} + '@dprint/linux-x64-musl@0.50.1': + resolution: {integrity: sha512-assISBbaKKL8LkjrIy/5tpE157MVW6HbyIKAjTtg3tPNM3lDn1oH3twuGtK9WBsN/VoEP3QMZVauolcUJT/VOg==} cpu: [x64] os: [linux] - '@dprint/win32-arm64@0.49.1': - resolution: {integrity: sha512-gQa4s82lMcXjfdxjWBQun6IJlXdPZZaIj2/2cqXWVEOYPKxAZ/JvGzt2pPG+i73h9KHjNLIV8M9ckqEH3oHufg==} + '@dprint/win32-arm64@0.50.1': + resolution: {integrity: sha512-ZeaRMQYoFjrsO3lvI1SqzDWDGH1GGXWmNSeXvcFuAf2OgYQJWMBlLotCKiHNJ3uyYneoyhTg2tv9QkApNkZV4Q==} cpu: [arm64] os: [win32] - '@dprint/win32-x64@0.49.1': - resolution: {integrity: sha512-nPU6+hoVze5JJlgET7woYWElBw0IUaB/9XKTaglknQuUUfsmD75D9pkgJTxdIxl9Bg/i5O7c9wb3Nj4XNiTIfw==} + '@dprint/win32-x64@0.50.1': + resolution: {integrity: sha512-pMm8l/hRZ9zYylKw/yCaYkSV3btYB9UyMDbWqyxNthkQ1gckWrk17VTI6WjwwQuHD4Iaz5JgAYLS36hlUzWkxA==} cpu: [x64] os: [win32] - '@esbuild/aix-ppc64@0.25.2': - resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.2': - resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.2': - resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.2': - resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.2': - resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.2': - resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.2': - resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.2': - resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.2': - resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.2': - resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.2': - resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.2': - resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.2': - resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.2': - resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.2': - resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.2': - resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.2': - resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.2': - resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.2': - resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.2': - resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.2': - resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.2': - resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.2': - resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.2': - resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.2': - resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -292,36 +298,36 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.20.0': - resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.2.1': - resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} + '@eslint/config-helpers@0.3.1': + resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.12.0': - resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.24.0': - resolution: {integrity: sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==} + '@eslint/js@9.34.0': + resolution: {integrity: sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.7': - resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@gerrit0/mini-shiki@3.2.2': - resolution: {integrity: sha512-vaZNGhGLKMY14HbF53xxHNgFO9Wz+t5lTlGNpl2N9xFiKQ0I5oIe0vKjU9dh7Nb3Dw6lZ7wqUE0ri+zcdpnK+Q==} + '@gerrit0/mini-shiki@3.12.0': + resolution: {integrity: sha512-CF1vkfe2ViPtmoFEvtUWilEc4dOCiFzV8+J7/vEISSsslKQ97FjeTPNMCqUhZEiKySmKRgK3UO/CxtkyOp7DvA==} '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -339,8 +345,8 @@ packages: resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} - '@humanwhocodes/retry@0.4.2': - resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} '@isaacs/cliui@8.0.2': @@ -355,11 +361,11 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.1': + resolution: {integrity: sha512-mBLKRHc7Ffw/hObYb9+cunuGNjshQk+vZdwZBJoqiysK/mW3Jq0UXosq8aIhMnLevANhR9yoYfdUEOHg6M9y0g==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.26': + resolution: {integrity: sha512-Z9rjt4BUVEbLFpw0qjCklVxxf421wrmcbP4w+LmBUxYCyJTYYSclgJD0YsCgGqQCtCIPiz7kjbYYJiAKhjJ3kA==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -377,22 +383,22 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@puppeteer/browsers@2.9.0': - resolution: {integrity: sha512-8+xM+cFydYET4X/5/3yZMHs7sjS6c9I6H5I3xJdb6cinzxWUT/I2QVw4avxCQ8QDndwdHkG/FiSZIrCjAbaKvQ==} + '@puppeteer/browsers@2.10.8': + resolution: {integrity: sha512-f02QYEnBDE0p8cteNoPYHHjbDuwyfbe4cCIVlNi8/MRicIxFW4w4CfgU0LNgWEID6s06P+hRJ1qjpBLMhPRCiQ==} engines: {node: '>=18'} hasBin: true - '@shikijs/engine-oniguruma@3.2.1': - resolution: {integrity: sha512-wZZAkayEn6qu2+YjenEoFqj0OyQI64EWsNR6/71d1EkG4sxEOFooowKivsWPpaWNBu3sxAG+zPz5kzBL/SsreQ==} + '@shikijs/engine-oniguruma@3.12.0': + resolution: {integrity: sha512-IfDl3oXPbJ/Jr2K8mLeQVpnF+FxjAc7ZPDkgr38uEw/Bg3u638neSrpwqOTnTHXt1aU0Fk1/J+/RBdst1kVqLg==} - '@shikijs/langs@3.2.1': - resolution: {integrity: sha512-If0iDHYRSGbihiA8+7uRsgb1er1Yj11pwpX1c6HLYnizDsKAw5iaT3JXj5ZpaimXSWky/IhxTm7C6nkiYVym+A==} + '@shikijs/langs@3.12.0': + resolution: {integrity: sha512-HIca0daEySJ8zuy9bdrtcBPhcYBo8wR1dyHk1vKrOuwDsITtZuQeGhEkcEfWc6IDyTcom7LRFCH6P7ljGSCEiQ==} - '@shikijs/themes@3.2.1': - resolution: {integrity: sha512-k5DKJUT8IldBvAm8WcrDT5+7GA7se6lLksR+2E3SvyqGTyFMzU2F9Gb7rmD+t+Pga1MKrYFxDIeyWjMZWM6uBQ==} + '@shikijs/themes@3.12.0': + resolution: {integrity: sha512-/lxvQxSI5s4qZLV/AuFaA4Wt61t/0Oka/P9Lmpr1UV+HydNCczO3DMHOC/CsXCCpbv4Zq8sMD0cDa7mvaVoj0Q==} - '@shikijs/types@3.2.1': - resolution: {integrity: sha512-/NTWAk4KE2M8uac0RhOsIhYQf4pdU0OywQuYDGIGAJ6Mjunxl2cGiuLkvu4HLCMn+OTTLRWkjZITp+aYJv60yA==} + '@shikijs/types@3.12.0': + resolution: {integrity: sha512-jsFzm8hCeTINC3OCmTZdhR9DOl/foJWplH2Px0bTi4m8z59fnsueLsweX82oGcjRQ7mfQAluQYKGoH2VzsWY4A==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -400,8 +406,8 @@ packages: '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -427,8 +433,8 @@ packages: '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@18.19.76': - resolution: {integrity: sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==} + '@types/node@18.19.113': + resolution: {integrity: sha512-TmSTE9vyebJ9vSEiU+P+0Sp4F5tMgjiEOZaQUW6wA3ODvi6uBgkHQ+EsIu0pbiKvf9QHEvyRCiaz03rV0b+IaA==} '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -436,51 +442,63 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.29.0': - resolution: {integrity: sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==} + '@typescript-eslint/eslint-plugin@8.41.0': + resolution: {integrity: sha512-8fz6oa6wEKZrhXWro/S3n2eRJqlRcIa6SlDh59FXJ5Wp5XRZ8B9ixpJDcjadHq47hMx0u+HW6SNa6LjJQ6NLtw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + '@typescript-eslint/parser': ^8.41.0 eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.29.0': - resolution: {integrity: sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==} + '@typescript-eslint/parser@8.41.0': + resolution: {integrity: sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.41.0': + resolution: {integrity: sha512-b8V9SdGBQzQdjJ/IO3eDifGpDBJfvrNTp2QD9P2BeqWTGrRibgfgIlBSw6z3b6R7dPzg752tOs4u/7yCLxksSQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.29.0': - resolution: {integrity: sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==} + '@typescript-eslint/scope-manager@8.41.0': + resolution: {integrity: sha512-n6m05bXn/Cd6DZDGyrpXrELCPVaTnLdPToyhBoFkLIMznRUQUEQdSp96s/pcWSQdqOhrgR1mzJ+yItK7T+WPMQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.29.0': - resolution: {integrity: sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==} + '@typescript-eslint/tsconfig-utils@8.41.0': + resolution: {integrity: sha512-TDhxYFPUYRFxFhuU5hTIJk+auzM/wKvWgoNYOPcOf6i4ReYlOoYN8q1dV5kOTjNQNJgzWN3TUUQMtlLOcUgdUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.41.0': + resolution: {integrity: sha512-63qt1h91vg3KsjVVonFJWjgSK7pZHSQFKH6uwqxAH9bBrsyRhO6ONoKyXxyVBzG1lJnFAJcKAcxLS54N1ee1OQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.29.0': - resolution: {integrity: sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==} + '@typescript-eslint/types@8.41.0': + resolution: {integrity: sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.29.0': - resolution: {integrity: sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==} + '@typescript-eslint/typescript-estree@8.41.0': + resolution: {integrity: sha512-D43UwUYJmGhuwHfY7MtNKRZMmfd8+p/eNSfFe6tH5mbVDto+VQCayeAt35rOx3Cs6wxD16DQtIKw/YXxt5E0UQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.29.0': - resolution: {integrity: sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==} + '@typescript-eslint/utils@8.41.0': + resolution: {integrity: sha512-udbCVstxZ5jiPIXrdH+BZWnPatjlYwJuJkDA4Tbo3WyYLh8NvB+h/bKeSZHDOFKfphsZYJQqaFtLeXEqurQn1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.29.0': - resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==} + '@typescript-eslint/visitor-keys@8.41.0': + resolution: {integrity: sha512-+GeGMebMCy0elMNg67LRNoVnUFPIm37iu5CmHESVx56/9Jsfdpsvbv605DQ81Pi/x11IdKUsS5nzgTYbCQU9fg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typestrong/fs-fixture-builder@https://codeload.github.com/TypeStrong/fs-fixture-builder/tar.gz/34113409e3a171e68ce5e2b55461ef5c35591cfe': @@ -493,8 +511,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} hasBin: true @@ -505,10 +523,6 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -525,10 +539,6 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -545,13 +555,18 @@ packages: bare-events@2.5.4: resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} - bare-fs@4.0.1: - resolution: {integrity: sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==} - engines: {bare: '>=1.7.0'} + bare-fs@4.1.5: + resolution: {integrity: sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true - bare-os@3.4.0: - resolution: {integrity: sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA==} - engines: {bare: '>=1.6.0'} + bare-os@3.6.1: + resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==} + engines: {bare: '>=1.14.0'} bare-path@3.0.0: resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} @@ -571,15 +586,11 @@ packages: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -613,12 +624,12 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} - chromium-bidi@3.0.0: - resolution: {integrity: sha512-ZOGRDAhBMX1uxL2Cm2TDuhImbrsEz5A/tTcVU6RpXEWaTNUNwsHW6njUXizh51Ir6iqHbKAfhA2XK33uBcLo5A==} + chromium-bidi@8.0.0: + resolution: {integrity: sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==} peerDependencies: devtools-protocol: '*' @@ -656,8 +667,8 @@ packages: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -676,15 +687,15 @@ packages: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} - devtools-protocol@0.0.1425554: - resolution: {integrity: sha512-uRfxR6Nlzdzt0ihVIkV+sLztKgs7rgquY/Mhcv1YNCWDh5IZgl5mnn2aeEnW5stYTE0wwiF4RYVz8eMEpV1SEw==} + devtools-protocol@0.0.1475386: + resolution: {integrity: sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==} - diff@5.2.0: - resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + diff@7.0.0: + resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} engines: {node: '>=0.3.1'} - dprint@0.49.1: - resolution: {integrity: sha512-pO9XH79SyXybj2Vhc9ITZMEI8cJkdlQQRoD8oEfPH6Jjpp/7WX5kIgECVd3DBOjjAdCSiW6R47v3gJBx/qZVkw==} + dprint@0.50.1: + resolution: {integrity: sha512-s+kUyQp2rGpwsM3vVmXySOY3v1NjYyRpKfQZdP4rfNTz6zQuICSO6nqIXNm3YdK1MwNFR/EXSFMuE1YPuulhow==} hasBin: true eastasianwidth@0.2.0: @@ -696,8 +707,8 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} @@ -710,8 +721,8 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - esbuild@0.25.2: - resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} engines: {node: '>=18'} hasBin: true @@ -728,20 +739,20 @@ packages: engines: {node: '>=6.0'} hasBin: true - eslint-scope@8.3.0: - resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.24.0: - resolution: {integrity: sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==} + eslint@9.34.0: + resolution: {integrity: sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -750,8 +761,8 @@ packages: jiti: optional: true - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esprima@4.0.1: @@ -796,8 +807,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.19.0: - resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} @@ -825,8 +836,8 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} fsevents@2.3.3: @@ -842,8 +853,8 @@ packages: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} - get-tsconfig@4.10.0: - resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} get-uri@6.0.4: resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} @@ -891,6 +902,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -906,10 +921,6 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1030,10 +1041,6 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -1045,8 +1052,8 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - mocha@11.1.0: - resolution: {integrity: sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==} + mocha@11.7.1: + resolution: {integrity: sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true @@ -1060,10 +1067,6 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1135,8 +1138,8 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - pump@3.0.2: - resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} @@ -1146,12 +1149,12 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - puppeteer-core@24.6.0: - resolution: {integrity: sha512-Cukxysy12m0v350bhl/Gzof0XQYmtON9l2VvGp3D4BOQZVgyf+y5wIpcjDZQ/896Okoi95dKRGRV8E6a7SYAQQ==} + puppeteer-core@24.17.1: + resolution: {integrity: sha512-Msh/kf9k1XFN0wuKiT4/npMmMWOT7kPBEUw01gWvRoKOOoz3It9TEmWjnt4Gl4eO+p73VMrvR+wfa0dm9rfxjw==} engines: {node: '>=18'} - puppeteer@24.6.0: - resolution: {integrity: sha512-wYTB8WkzAr7acrlsp+0at1PZjOJPOxe6dDWKOG/kaX4Zjck9RXCFx3CtsxsAGzPn/Yv6AzgJC/CW1P5l+qxsqw==} + puppeteer@24.17.1: + resolution: {integrity: sha512-KIuX0w+0um4TUbm55yFl2WIsbgjya2BHIgW9ylTuhavtwjXCOM7lMo9oLR1jQnCxrFvm9h/Yeb+zfs4nlgntPg==} engines: {node: '>=18'} hasBin: true @@ -1161,9 +1164,9 @@ packages: randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} @@ -1176,8 +1179,8 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} run-parallel@1.2.0: @@ -1186,8 +1189,8 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - semver@7.7.1: - resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} hasBin: true @@ -1214,8 +1217,8 @@ packages: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} - socks@2.8.4: - resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} + socks@2.8.5: + resolution: {integrity: sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} source-map@0.6.1: @@ -1225,8 +1228,8 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - streamx@2.22.0: - resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} + streamx@2.22.1: + resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -1256,8 +1259,8 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - tar-fs@3.0.8: - resolution: {integrity: sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==} + tar-fs@3.1.0: + resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} @@ -1273,8 +1276,8 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - ts-api-utils@2.0.1: - resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -1282,8 +1285,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.19.3: - resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} + tsx@4.20.5: + resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==} engines: {node: '>=18.0.0'} hasBin: true @@ -1294,15 +1297,15 @@ packages: typed-query-selector@2.12.0: resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} - typescript-eslint@8.29.0: - resolution: {integrity: sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==} + typescript-eslint@8.41.0: + resolution: {integrity: sha512-n66rzs5OBXW3SFSnZHr2T685q1i4ODm2nulFJhMZBotaTavsS8TrI3d7bDlRSs9yWo7HmyWrN9qDu14Qv7Y0Dw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true @@ -1328,8 +1331,8 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - workerpool@6.5.1: - resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + workerpool@9.3.3: + resolution: {integrity: sha512-slxCaKbYjEdFT/o2rH9xS1hf4uRDch1w7Uo+apxhZ+sf/1d9e0ZVkn42kPNGP2dgjIx6YFvSevj0zHvbWe2jdw==} wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} @@ -1342,8 +1345,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.1: - resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1358,9 +1361,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yaml@2.7.1: - resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} - engines: {node: '>= 14'} + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} hasBin: true yargs-parser@21.1.1: @@ -1382,149 +1385,152 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zod@3.24.2: - resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + zod@3.25.67: + resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} snapshots: - '@babel/code-frame@7.26.2': + '@babel/code-frame@7.27.1': dependencies: - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-validator-identifier': 7.27.1 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-identifier@7.27.1': {} '@bcoe/v8-coverage@1.0.2': {} - '@dprint/darwin-arm64@0.49.1': + '@dprint/darwin-arm64@0.50.1': + optional: true + + '@dprint/darwin-x64@0.50.1': optional: true - '@dprint/darwin-x64@0.49.1': + '@dprint/linux-arm64-glibc@0.50.1': optional: true - '@dprint/linux-arm64-glibc@0.49.1': + '@dprint/linux-arm64-musl@0.50.1': optional: true - '@dprint/linux-arm64-musl@0.49.1': + '@dprint/linux-riscv64-glibc@0.50.1': optional: true - '@dprint/linux-riscv64-glibc@0.49.1': + '@dprint/linux-x64-glibc@0.50.1': optional: true - '@dprint/linux-x64-glibc@0.49.1': + '@dprint/linux-x64-musl@0.50.1': optional: true - '@dprint/linux-x64-musl@0.49.1': + '@dprint/win32-arm64@0.50.1': optional: true - '@dprint/win32-arm64@0.49.1': + '@dprint/win32-x64@0.50.1': optional: true - '@dprint/win32-x64@0.49.1': + '@esbuild/aix-ppc64@0.25.9': optional: true - '@esbuild/aix-ppc64@0.25.2': + '@esbuild/android-arm64@0.25.9': optional: true - '@esbuild/android-arm64@0.25.2': + '@esbuild/android-arm@0.25.9': optional: true - '@esbuild/android-arm@0.25.2': + '@esbuild/android-x64@0.25.9': optional: true - '@esbuild/android-x64@0.25.2': + '@esbuild/darwin-arm64@0.25.9': optional: true - '@esbuild/darwin-arm64@0.25.2': + '@esbuild/darwin-x64@0.25.9': optional: true - '@esbuild/darwin-x64@0.25.2': + '@esbuild/freebsd-arm64@0.25.9': optional: true - '@esbuild/freebsd-arm64@0.25.2': + '@esbuild/freebsd-x64@0.25.9': optional: true - '@esbuild/freebsd-x64@0.25.2': + '@esbuild/linux-arm64@0.25.9': optional: true - '@esbuild/linux-arm64@0.25.2': + '@esbuild/linux-arm@0.25.9': optional: true - '@esbuild/linux-arm@0.25.2': + '@esbuild/linux-ia32@0.25.9': optional: true - '@esbuild/linux-ia32@0.25.2': + '@esbuild/linux-loong64@0.25.9': optional: true - '@esbuild/linux-loong64@0.25.2': + '@esbuild/linux-mips64el@0.25.9': optional: true - '@esbuild/linux-mips64el@0.25.2': + '@esbuild/linux-ppc64@0.25.9': optional: true - '@esbuild/linux-ppc64@0.25.2': + '@esbuild/linux-riscv64@0.25.9': optional: true - '@esbuild/linux-riscv64@0.25.2': + '@esbuild/linux-s390x@0.25.9': optional: true - '@esbuild/linux-s390x@0.25.2': + '@esbuild/linux-x64@0.25.9': optional: true - '@esbuild/linux-x64@0.25.2': + '@esbuild/netbsd-arm64@0.25.9': optional: true - '@esbuild/netbsd-arm64@0.25.2': + '@esbuild/netbsd-x64@0.25.9': optional: true - '@esbuild/netbsd-x64@0.25.2': + '@esbuild/openbsd-arm64@0.25.9': optional: true - '@esbuild/openbsd-arm64@0.25.2': + '@esbuild/openbsd-x64@0.25.9': optional: true - '@esbuild/openbsd-x64@0.25.2': + '@esbuild/openharmony-arm64@0.25.9': optional: true - '@esbuild/sunos-x64@0.25.2': + '@esbuild/sunos-x64@0.25.9': optional: true - '@esbuild/win32-arm64@0.25.2': + '@esbuild/win32-arm64@0.25.9': optional: true - '@esbuild/win32-ia32@0.25.2': + '@esbuild/win32-ia32@0.25.9': optional: true - '@esbuild/win32-x64@0.25.2': + '@esbuild/win32-x64@0.25.9': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.24.0)': + '@eslint-community/eslint-utils@4.7.0(eslint@9.34.0)': dependencies: - eslint: 9.24.0 + eslint: 9.34.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/config-array@0.20.0': + '@eslint/config-array@0.21.0': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.2.1': {} + '@eslint/config-helpers@0.3.1': {} - '@eslint/core@0.12.0': + '@eslint/core@0.15.2': dependencies: '@types/json-schema': 7.0.15 '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.0(supports-color@8.1.1) - espree: 10.3.0 + debug: 4.4.1(supports-color@8.1.1) + espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 @@ -1534,21 +1540,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.24.0': {} + '@eslint/js@9.34.0': {} '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.2.7': + '@eslint/plugin-kit@0.3.5': dependencies: - '@eslint/core': 0.12.0 + '@eslint/core': 0.15.2 levn: 0.4.1 - '@gerrit0/mini-shiki@3.2.2': + '@gerrit0/mini-shiki@3.12.0': dependencies: - '@shikijs/engine-oniguruma': 3.2.1 - '@shikijs/langs': 3.2.1 - '@shikijs/themes': 3.2.1 - '@shikijs/types': 3.2.1 + '@shikijs/engine-oniguruma': 3.12.0 + '@shikijs/langs': 3.12.0 + '@shikijs/themes': 3.12.0 + '@shikijs/types': 3.12.0 '@shikijs/vscode-textmate': 10.0.2 '@humanfs/core@0.19.1': {} @@ -1562,7 +1568,7 @@ snapshots: '@humanwhocodes/retry@0.3.1': {} - '@humanwhocodes/retry@0.4.2': {} + '@humanwhocodes/retry@0.4.3': {} '@isaacs/cliui@8.0.2': dependencies: @@ -1577,12 +1583,12 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.1': {} - '@jridgewell/trace-mapping@0.3.25': + '@jridgewell/trace-mapping@0.3.26': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.1 '@nodelib/fs.scandir@2.1.5': dependencies: @@ -1594,38 +1600,38 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.0 + fastq: 1.19.1 '@pkgjs/parseargs@0.11.0': optional: true - '@puppeteer/browsers@2.9.0': + '@puppeteer/browsers@2.10.8': dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1(supports-color@8.1.1) extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.5.0 - semver: 7.7.1 - tar-fs: 3.0.8 + semver: 7.7.2 + tar-fs: 3.1.0 yargs: 17.7.2 transitivePeerDependencies: - bare-buffer - supports-color - '@shikijs/engine-oniguruma@3.2.1': + '@shikijs/engine-oniguruma@3.12.0': dependencies: - '@shikijs/types': 3.2.1 + '@shikijs/types': 3.12.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.2.1': + '@shikijs/langs@3.12.0': dependencies: - '@shikijs/types': 3.2.1 + '@shikijs/types': 3.12.0 - '@shikijs/themes@3.2.1': + '@shikijs/themes@3.12.0': dependencies: - '@shikijs/types': 3.2.1 + '@shikijs/types': 3.12.0 - '@shikijs/types@3.2.1': + '@shikijs/types@3.12.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -1634,7 +1640,7 @@ snapshots: '@tootallnate/quickjs-emscripten@0.23.0': {} - '@types/estree@1.0.6': {} + '@types/estree@1.0.8': {} '@types/hast@3.0.4': dependencies: @@ -1657,7 +1663,7 @@ snapshots: '@types/mocha@10.0.10': {} - '@types/node@18.19.76': + '@types/node@18.19.113': dependencies: undici-types: 5.26.5 @@ -1665,93 +1671,109 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 18.19.76 + '@types/node': 18.19.113 optional: true - '@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.24.0)(typescript@5.8.3))(eslint@9.24.0)(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.41.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0)(typescript@5.9.2))(eslint@9.34.0)(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.29.0(eslint@9.24.0)(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.29.0 - '@typescript-eslint/type-utils': 8.29.0(eslint@9.24.0)(typescript@5.8.3) - '@typescript-eslint/utils': 8.29.0(eslint@9.24.0)(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.29.0 - eslint: 9.24.0 + '@typescript-eslint/parser': 8.41.0(eslint@9.34.0)(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.41.0 + '@typescript-eslint/type-utils': 8.41.0(eslint@9.34.0)(typescript@5.9.2) + '@typescript-eslint/utils': 8.41.0(eslint@9.34.0)(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.41.0 + eslint: 9.34.0 graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.0.1(typescript@5.8.3) - typescript: 5.8.3 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.41.0(eslint@9.34.0)(typescript@5.9.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.41.0 + '@typescript-eslint/types': 8.41.0 + '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.41.0 + debug: 4.4.1(supports-color@8.1.1) + eslint: 9.34.0 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.29.0(eslint@9.24.0)(typescript@5.8.3)': + '@typescript-eslint/project-service@8.41.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/scope-manager': 8.29.0 - '@typescript-eslint/types': 8.29.0 - '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.29.0 - debug: 4.4.0(supports-color@8.1.1) - eslint: 9.24.0 - typescript: 5.8.3 + '@typescript-eslint/tsconfig-utils': 8.41.0(typescript@5.9.2) + '@typescript-eslint/types': 8.41.0 + debug: 4.4.1(supports-color@8.1.1) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.29.0': + '@typescript-eslint/scope-manager@8.41.0': + dependencies: + '@typescript-eslint/types': 8.41.0 + '@typescript-eslint/visitor-keys': 8.41.0 + + '@typescript-eslint/tsconfig-utils@8.41.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.29.0 - '@typescript-eslint/visitor-keys': 8.29.0 + typescript: 5.9.2 - '@typescript-eslint/type-utils@8.29.0(eslint@9.24.0)(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.41.0(eslint@9.34.0)(typescript@5.9.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.29.0(eslint@9.24.0)(typescript@5.8.3) - debug: 4.4.0(supports-color@8.1.1) - eslint: 9.24.0 - ts-api-utils: 2.0.1(typescript@5.8.3) - typescript: 5.8.3 + '@typescript-eslint/types': 8.41.0 + '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.41.0(eslint@9.34.0)(typescript@5.9.2) + debug: 4.4.1(supports-color@8.1.1) + eslint: 9.34.0 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.29.0': {} + '@typescript-eslint/types@8.41.0': {} - '@typescript-eslint/typescript-estree@8.29.0(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@8.41.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.29.0 - '@typescript-eslint/visitor-keys': 8.29.0 - debug: 4.4.0(supports-color@8.1.1) + '@typescript-eslint/project-service': 8.41.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.41.0(typescript@5.9.2) + '@typescript-eslint/types': 8.41.0 + '@typescript-eslint/visitor-keys': 8.41.0 + debug: 4.4.1(supports-color@8.1.1) fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 - ts-api-utils: 2.0.1(typescript@5.8.3) - typescript: 5.8.3 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.29.0(eslint@9.24.0)(typescript@5.8.3)': + '@typescript-eslint/utils@8.41.0(eslint@9.34.0)(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.24.0) - '@typescript-eslint/scope-manager': 8.29.0 - '@typescript-eslint/types': 8.29.0 - '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.3) - eslint: 9.24.0 - typescript: 5.8.3 + '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0) + '@typescript-eslint/scope-manager': 8.41.0 + '@typescript-eslint/types': 8.41.0 + '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) + eslint: 9.34.0 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.29.0': + '@typescript-eslint/visitor-keys@8.41.0': dependencies: - '@typescript-eslint/types': 8.29.0 - eslint-visitor-keys: 4.2.0 + '@typescript-eslint/types': 8.41.0 + eslint-visitor-keys: 4.2.1 '@typestrong/fs-fixture-builder@https://codeload.github.com/TypeStrong/fs-fixture-builder/tar.gz/34113409e3a171e68ce5e2b55461ef5c35591cfe': {} - acorn-jsx@5.3.2(acorn@8.14.0): + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: - acorn: 8.14.0 + acorn: 8.15.0 - acorn@8.14.0: {} + acorn@8.15.0: {} agent-base@7.1.3: {} @@ -1762,8 +1784,6 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ansi-colors@4.1.3: {} - ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -1774,11 +1794,6 @@ snapshots: ansi-styles@6.2.1: {} - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - argparse@2.0.1: {} ast-types@0.13.4: @@ -1792,40 +1807,36 @@ snapshots: bare-events@2.5.4: optional: true - bare-fs@4.0.1: + bare-fs@4.1.5: dependencies: bare-events: 2.5.4 bare-path: 3.0.0 bare-stream: 2.6.5(bare-events@2.5.4) - transitivePeerDependencies: - - bare-buffer optional: true - bare-os@3.4.0: + bare-os@3.6.1: optional: true bare-path@3.0.0: dependencies: - bare-os: 3.4.0 + bare-os: 3.6.1 optional: true bare-stream@2.6.5(bare-events@2.5.4): dependencies: - streamx: 2.22.0 + streamx: 2.22.1 optionalDependencies: bare-events: 2.5.4 optional: true basic-ftp@5.0.5: {} - binary-extensions@2.3.0: {} - - brace-expansion@1.1.11: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -1842,7 +1853,7 @@ snapshots: '@bcoe/v8-coverage': 1.0.2 '@istanbuljs/schema': 0.1.3 find-up: 5.0.0 - foreground-child: 3.3.0 + foreground-child: 3.3.1 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-reports: 3.1.7 @@ -1860,23 +1871,15 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chokidar@3.6.0: + chokidar@4.0.3: dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 + readdirp: 4.1.2 - chromium-bidi@3.0.0(devtools-protocol@0.0.1425554): + chromium-bidi@8.0.0(devtools-protocol@0.0.1475386): dependencies: - devtools-protocol: 0.0.1425554 + devtools-protocol: 0.0.1475386 mitt: 3.0.1 - zod: 3.24.2 + zod: 3.25.67 cliui@8.0.1: dependencies: @@ -1894,14 +1897,14 @@ snapshots: convert-source-map@2.0.0: {} - cosmiconfig@9.0.0(typescript@5.8.3): + cosmiconfig@9.0.0(typescript@5.9.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 cross-spawn@7.0.6: dependencies: @@ -1911,7 +1914,7 @@ snapshots: data-uri-to-buffer@6.0.2: {} - debug@4.4.0(supports-color@8.1.1): + debug@4.4.1(supports-color@8.1.1): dependencies: ms: 2.1.3 optionalDependencies: @@ -1927,21 +1930,21 @@ snapshots: escodegen: 2.1.0 esprima: 4.0.1 - devtools-protocol@0.0.1425554: {} + devtools-protocol@0.0.1475386: {} - diff@5.2.0: {} + diff@7.0.0: {} - dprint@0.49.1: + dprint@0.50.1: optionalDependencies: - '@dprint/darwin-arm64': 0.49.1 - '@dprint/darwin-x64': 0.49.1 - '@dprint/linux-arm64-glibc': 0.49.1 - '@dprint/linux-arm64-musl': 0.49.1 - '@dprint/linux-riscv64-glibc': 0.49.1 - '@dprint/linux-x64-glibc': 0.49.1 - '@dprint/linux-x64-musl': 0.49.1 - '@dprint/win32-arm64': 0.49.1 - '@dprint/win32-x64': 0.49.1 + '@dprint/darwin-arm64': 0.50.1 + '@dprint/darwin-x64': 0.50.1 + '@dprint/linux-arm64-glibc': 0.50.1 + '@dprint/linux-arm64-musl': 0.50.1 + '@dprint/linux-riscv64-glibc': 0.50.1 + '@dprint/linux-x64-glibc': 0.50.1 + '@dprint/linux-x64-musl': 0.50.1 + '@dprint/win32-arm64': 0.50.1 + '@dprint/win32-x64': 0.50.1 eastasianwidth@0.2.0: {} @@ -1949,7 +1952,7 @@ snapshots: emoji-regex@9.2.2: {} - end-of-stream@1.4.4: + end-of-stream@1.4.5: dependencies: once: 1.4.0 @@ -1961,33 +1964,34 @@ snapshots: dependencies: is-arrayish: 0.2.1 - esbuild@0.25.2: + esbuild@0.25.9: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.2 - '@esbuild/android-arm': 0.25.2 - '@esbuild/android-arm64': 0.25.2 - '@esbuild/android-x64': 0.25.2 - '@esbuild/darwin-arm64': 0.25.2 - '@esbuild/darwin-x64': 0.25.2 - '@esbuild/freebsd-arm64': 0.25.2 - '@esbuild/freebsd-x64': 0.25.2 - '@esbuild/linux-arm': 0.25.2 - '@esbuild/linux-arm64': 0.25.2 - '@esbuild/linux-ia32': 0.25.2 - '@esbuild/linux-loong64': 0.25.2 - '@esbuild/linux-mips64el': 0.25.2 - '@esbuild/linux-ppc64': 0.25.2 - '@esbuild/linux-riscv64': 0.25.2 - '@esbuild/linux-s390x': 0.25.2 - '@esbuild/linux-x64': 0.25.2 - '@esbuild/netbsd-arm64': 0.25.2 - '@esbuild/netbsd-x64': 0.25.2 - '@esbuild/openbsd-arm64': 0.25.2 - '@esbuild/openbsd-x64': 0.25.2 - '@esbuild/sunos-x64': 0.25.2 - '@esbuild/win32-arm64': 0.25.2 - '@esbuild/win32-ia32': 0.25.2 - '@esbuild/win32-x64': 0.25.2 + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 escalade@3.2.0: {} @@ -2001,38 +2005,38 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-scope@8.3.0: + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.0: {} + eslint-visitor-keys@4.2.1: {} - eslint@9.24.0: + eslint@9.34.0: dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.24.0) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.20.0 - '@eslint/config-helpers': 0.2.1 - '@eslint/core': 0.12.0 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.24.0 - '@eslint/plugin-kit': 0.2.7 + '@eslint/js': 9.34.0 + '@eslint/plugin-kit': 0.3.5 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.2 - '@types/estree': 1.0.6 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1(supports-color@8.1.1) escape-string-regexp: 4.0.0 - eslint-scope: 8.3.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -2050,11 +2054,11 @@ snapshots: transitivePeerDependencies: - supports-color - espree@10.3.0: + espree@10.4.0: dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 4.2.0 + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 esprima@4.0.1: {} @@ -2072,7 +2076,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -2096,9 +2100,9 @@ snapshots: fast-levenshtein@2.0.6: {} - fastq@1.19.0: + fastq@1.19.1: dependencies: - reusify: 1.0.4 + reusify: 1.1.0 fd-slicer@1.1.0: dependencies: @@ -2126,7 +2130,7 @@ snapshots: flatted@3.3.3: {} - foreground-child@3.3.0: + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 @@ -2138,9 +2142,9 @@ snapshots: get-stream@5.2.0: dependencies: - pump: 3.0.2 + pump: 3.0.3 - get-tsconfig@4.10.0: + get-tsconfig@4.10.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -2148,7 +2152,7 @@ snapshots: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -2162,7 +2166,7 @@ snapshots: glob@10.4.5: dependencies: - foreground-child: 3.3.0 + foreground-child: 3.3.1 jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 @@ -2182,19 +2186,21 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color ignore@5.3.2: {} + ignore@7.0.5: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -2209,10 +2215,6 @@ snapshots: is-arrayish@0.2.1: {} - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -2298,7 +2300,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.1 + semver: 7.7.2 markdown-it@14.1.0: dependencies: @@ -2320,39 +2322,35 @@ snapshots: minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 - - minimatch@5.1.6: - dependencies: - brace-expansion: 2.0.1 + brace-expansion: 1.1.12 minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minipass@7.1.2: {} mitt@3.0.1: {} - mocha@11.1.0: + mocha@11.7.1: dependencies: - ansi-colors: 4.1.3 browser-stdout: 1.3.1 - chokidar: 3.6.0 - debug: 4.4.0(supports-color@8.1.1) - diff: 5.2.0 + chokidar: 4.0.3 + debug: 4.4.1(supports-color@8.1.1) + diff: 7.0.0 escape-string-regexp: 4.0.0 find-up: 5.0.0 glob: 10.4.5 he: 1.2.0 js-yaml: 4.1.0 log-symbols: 4.1.0 - minimatch: 5.1.6 + minimatch: 9.0.5 ms: 2.1.3 + picocolors: 1.1.1 serialize-javascript: 6.0.2 strip-json-comments: 3.1.1 supports-color: 8.1.1 - workerpool: 6.5.1 + workerpool: 9.3.3 yargs: 17.7.2 yargs-parser: 21.1.1 yargs-unparser: 2.0.0 @@ -2363,8 +2361,6 @@ snapshots: netmask@2.0.2: {} - normalize-path@3.0.0: {} - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -2390,7 +2386,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1(supports-color@8.1.1) get-uri: 6.0.4 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -2412,7 +2408,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.26.2 + '@babel/code-frame': 7.27.1 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -2439,7 +2435,7 @@ snapshots: proxy-agent@6.5.0: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1(supports-color@8.1.1) http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -2451,36 +2447,36 @@ snapshots: proxy-from-env@1.1.0: {} - pump@3.0.2: + pump@3.0.3: dependencies: - end-of-stream: 1.4.4 + end-of-stream: 1.4.5 once: 1.4.0 punycode.js@2.3.1: {} punycode@2.3.1: {} - puppeteer-core@24.6.0: + puppeteer-core@24.17.1: dependencies: - '@puppeteer/browsers': 2.9.0 - chromium-bidi: 3.0.0(devtools-protocol@0.0.1425554) - debug: 4.4.0(supports-color@8.1.1) - devtools-protocol: 0.0.1425554 + '@puppeteer/browsers': 2.10.8 + chromium-bidi: 8.0.0(devtools-protocol@0.0.1475386) + debug: 4.4.1(supports-color@8.1.1) + devtools-protocol: 0.0.1475386 typed-query-selector: 2.12.0 - ws: 8.18.1 + ws: 8.18.3 transitivePeerDependencies: - bare-buffer - bufferutil - supports-color - utf-8-validate - puppeteer@24.6.0(typescript@5.8.3): + puppeteer@24.17.1(typescript@5.9.2): dependencies: - '@puppeteer/browsers': 2.9.0 - chromium-bidi: 3.0.0(devtools-protocol@0.0.1425554) - cosmiconfig: 9.0.0(typescript@5.8.3) - devtools-protocol: 0.0.1425554 - puppeteer-core: 24.6.0 + '@puppeteer/browsers': 2.10.8 + chromium-bidi: 8.0.0(devtools-protocol@0.0.1475386) + cosmiconfig: 9.0.0(typescript@5.9.2) + devtools-protocol: 0.0.1475386 + puppeteer-core: 24.17.1 typed-query-selector: 2.12.0 transitivePeerDependencies: - bare-buffer @@ -2495,9 +2491,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 + readdirp@4.1.2: {} require-directory@2.1.1: {} @@ -2505,7 +2499,7 @@ snapshots: resolve-pkg-maps@1.0.0: {} - reusify@1.0.4: {} + reusify@1.1.0: {} run-parallel@1.2.0: dependencies: @@ -2513,7 +2507,7 @@ snapshots: safe-buffer@5.2.1: {} - semver@7.7.1: {} + semver@7.7.2: {} serialize-javascript@6.0.2: dependencies: @@ -2532,12 +2526,12 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) - socks: 2.8.4 + debug: 4.4.1(supports-color@8.1.1) + socks: 2.8.5 transitivePeerDependencies: - supports-color - socks@2.8.4: + socks@2.8.5: dependencies: ip-address: 9.0.5 smart-buffer: 4.2.0 @@ -2547,7 +2541,7 @@ snapshots: sprintf-js@1.1.3: {} - streamx@2.22.0: + streamx@2.22.1: dependencies: fast-fifo: 1.3.2 text-decoder: 1.2.3 @@ -2584,12 +2578,12 @@ snapshots: dependencies: has-flag: 4.0.0 - tar-fs@3.0.8: + tar-fs@3.1.0: dependencies: - pump: 3.0.2 + pump: 3.0.3 tar-stream: 3.1.7 optionalDependencies: - bare-fs: 4.0.1 + bare-fs: 4.1.5 bare-path: 3.0.0 transitivePeerDependencies: - bare-buffer @@ -2598,7 +2592,7 @@ snapshots: dependencies: b4a: 1.6.7 fast-fifo: 1.3.2 - streamx: 2.22.0 + streamx: 2.22.1 test-exclude@7.0.1: dependencies: @@ -2614,16 +2608,16 @@ snapshots: dependencies: is-number: 7.0.0 - ts-api-utils@2.0.1(typescript@5.8.3): + ts-api-utils@2.1.0(typescript@5.9.2): dependencies: - typescript: 5.8.3 + typescript: 5.9.2 tslib@2.8.1: {} - tsx@4.19.3: + tsx@4.20.5: dependencies: - esbuild: 0.25.2 - get-tsconfig: 4.10.0 + esbuild: 0.25.9 + get-tsconfig: 4.10.1 optionalDependencies: fsevents: 2.3.3 @@ -2633,17 +2627,18 @@ snapshots: typed-query-selector@2.12.0: {} - typescript-eslint@8.29.0(eslint@9.24.0)(typescript@5.8.3): + typescript-eslint@8.41.0(eslint@9.34.0)(typescript@5.9.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.24.0)(typescript@5.8.3))(eslint@9.24.0)(typescript@5.8.3) - '@typescript-eslint/parser': 8.29.0(eslint@9.24.0)(typescript@5.8.3) - '@typescript-eslint/utils': 8.29.0(eslint@9.24.0)(typescript@5.8.3) - eslint: 9.24.0 - typescript: 5.8.3 + '@typescript-eslint/eslint-plugin': 8.41.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0)(typescript@5.9.2))(eslint@9.34.0)(typescript@5.9.2) + '@typescript-eslint/parser': 8.41.0(eslint@9.34.0)(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.41.0(eslint@9.34.0)(typescript@5.9.2) + eslint: 9.34.0 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - typescript@5.8.3: {} + typescript@5.9.2: {} uc.micro@2.1.0: {} @@ -2655,7 +2650,7 @@ snapshots: v8-to-istanbul@9.3.0: dependencies: - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.26 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 @@ -2665,7 +2660,7 @@ snapshots: word-wrap@1.2.5: {} - workerpool@6.5.1: {} + workerpool@9.3.3: {} wrap-ansi@7.0.0: dependencies: @@ -2681,11 +2676,11 @@ snapshots: wrappy@1.0.2: {} - ws@8.18.1: {} + ws@8.18.3: {} y18n@5.0.8: {} - yaml@2.7.1: {} + yaml@2.8.1: {} yargs-parser@21.1.1: {} @@ -2713,4 +2708,4 @@ snapshots: yocto-queue@0.1.0: {} - zod@3.24.2: {} + zod@3.25.67: {} diff --git a/scripts/build_site.sh b/scripts/build_site.sh index 6c8276895..11622b4d9 100755 --- a/scripts/build_site.sh +++ b/scripts/build_site.sh @@ -14,6 +14,10 @@ fi if [[ -n "$CI" || ! -d example/docs ]]; then cd example pnpm i + # Ignoring warnings here because we inherit from Array, which results in + # a few warnings because the docs in the .d.ts have bad @param comments + # We might want to change TypeDoc's validation logic to make this not a + # warning at some point if the relevant comments show up on both signatures. pnpm run typedoc --logLevel Error cd .. fi @@ -22,7 +26,7 @@ fi git show $(git describe --tags --abbrev=0):CHANGELOG.md | sed 's/#* Unreleased//' > site/generated/CHANGELOG.md # Build the actual site, references the API docs -node bin/typedoc --options site/typedoc.config.jsonc +node bin/typedoc --options site/typedoc.config.jsonc --treatWarningsAsErrors # Create/copy static files node scripts/generate_options_schema.js docs-site/schema.json diff --git a/scripts/clone_api_users.js b/scripts/clone_api_users.js index 3a77f5e6e..d38c890db 100755 --- a/scripts/clone_api_users.js +++ b/scripts/clone_api_users.js @@ -93,6 +93,7 @@ if (import.meta.url.endsWith(process.argv[1])) { } console.log(`Cloning/updating took ${(Date.now() - start) / 1000} seconds`); + console.log(`Output is in ${args.values.output}`); // Check for repos listed in the wrong list const currentMinor = semver.parse(JSON.parse(readFileSync("package.json", "utf-8")).version)?.minor; diff --git a/scripts/generate_options_schema.js b/scripts/generate_options_schema.js index 74f880ba0..8c8375f3f 100644 --- a/scripts/generate_options_schema.js +++ b/scripts/generate_options_schema.js @@ -1,9 +1,13 @@ // @ts-check import { writeFileSync } from "fs"; -import { Internationalization, ParameterType } from "../dist/index.js"; +import { ParameterType } from "../dist/index.js"; import { addTypeDocOptions } from "../dist/lib/utils/options/sources/typedoc.js"; import { SORT_STRATEGIES } from "../dist/lib/utils/sort.js"; +import { setTranslations } from "#utils"; +import { loadTranslations } from "../dist/lib/internationalization/internationalization.js"; + +setTranslations(loadTranslations("en")); const IGNORED_OPTIONS = new Set(["help", "version"]); @@ -32,6 +36,7 @@ addTypeDocOptions({ case ParameterType.GlobArray: case ParameterType.PathArray: case ParameterType.ModuleArray: + case ParameterType.PluginArray: data.type = "array"; data.items = { type: "string" }; data.default = /** @type {import("../dist/index.js").ArrayDeclarationOption} */ ( diff --git a/scripts/generate_site_plugins.js b/scripts/generate_site_plugins.js index e7889a8f3..7e0fde228 100644 --- a/scripts/generate_site_plugins.js +++ b/scripts/generate_site_plugins.js @@ -8,7 +8,6 @@ const NEXT_BREAKING_TYPEDOC_VERSION = semver.parse(TYPEDOC_VERSION)?.inc("minor" if (!NEXT_BREAKING_TYPEDOC_VERSION) { throw new Error("Failed to determine next TypeDoc version"); } -console.log(NEXT_BREAKING_TYPEDOC_VERSION); const CACHE_ROOT = "tmp/site-cache"; mkdirSync(CACHE_ROOT, { recursive: true }); diff --git a/scripts/rebuild_specs.js b/scripts/rebuild_specs.js old mode 100644 new mode 100755 index cbdcba2dc..a0a00cd99 --- a/scripts/rebuild_specs.js +++ b/scripts/rebuild_specs.js @@ -1,3 +1,4 @@ +#!/usr/bin/env node // @ts-check "use strict"; @@ -80,7 +81,7 @@ const conversions = [ */ function rebuildConverterTests(dirs) { const program = ts.createProgram(app.options.getFileNames(), { - ...app.options.getCompilerOptions(), + ...app.options.getCompilerOptions(app.logger), noEmit: true, }); diff --git a/scripts/testcase.js b/scripts/testcase.js old mode 100644 new mode 100755 index 365bbe84d..8239e71cc --- a/scripts/testcase.js +++ b/scripts/testcase.js @@ -1,3 +1,4 @@ +#!/usr/bin/env node // @ts-check import md from "markdown-it"; import cp from "child_process"; @@ -55,17 +56,20 @@ async function main() { ["ts", "tsx", "js", "jsx"].includes(tok.info || ""), ) || tokens.find((tok) => tok.tag === "code"); + /** @type {string} */ + let file; if (!code) { console.log("No codeblock found"); - const file = `src/test/converter2/issues/gh${issue}.ts`; - await exec(`code ${file} src/test/issues.c2.test.ts`); - return; + file = `src/test/converter2/issues/gh${issue}.ts`; + await writeFile(file, ""); + } else { + const ext = process.argv[3] ? `.${process.argv[3]}` : guessExtension(code); + file = `src/test/converter2/issues/gh${issue}${ext}`; + await writeFile(file, code.content); } - const ext = process.argv[3] ? `.${process.argv[3]}` : guessExtension(code); - const file = `src/test/converter2/issues/gh${issue}${ext}`; - await writeFile(file, code.content); - await exec(`code ${file} src/test/issues.c2.test.ts`); + console.log(file); + console.log("src/test/issues.c2.test.ts"); } void main(); diff --git a/scripts/visual_regression.js b/scripts/visual_regression.js old mode 100644 new mode 100755 index f2c84d3d5..8f3be17aa --- a/scripts/visual_regression.js +++ b/scripts/visual_regression.js @@ -1,3 +1,4 @@ +#!/usr/bin/env node // @ts-check import util from "util"; import { cpSync, existsSync, mkdirSync, rmSync } from "fs"; diff --git a/site/declaration-references.md b/site/declaration-references.md index 18ed72494..d0213666c 100644 --- a/site/declaration-references.md +++ b/site/declaration-references.md @@ -4,10 +4,10 @@ title: Declaration References # Declaration References -> [!note] -> If [--useTsLinkResolution](options/comments.md#usetslinkresolution) is turned on (the default) this page likely -> **does not apply** for your links. Declaration references are used only if that option is off or TypeScript -> fails to resolve a link. +> [!note] If [--useTsLinkResolution](options/comments.md#usetslinkresolution) is turned on (the default) this page +> likely **does not apply** for your links within comments (though it will be used for +> [external documents](./external-documents.md) and for the readme file). Declaration references are used only if that option is +> off or TypeScript fails to resolve a link. Some tags like [`{@link}`](tags/link.md) and [`{@inheritDoc}`](tags/inheritDoc.md) can refer to other members of the documentation. These tags use declaration references to name another declaration. diff --git a/site/doc-comments/index.md b/site/doc-comments/index.md index 8aeb790c2..f0e2c7441 100644 --- a/site/doc-comments/index.md +++ b/site/doc-comments/index.md @@ -27,6 +27,9 @@ TypeDoc supports code blocks in markdown and uses the syntax highlighting theme with the [`--lightHighlightTheme`](../options/output.md#lighthighlighttheme) and [`--darkHighlightTheme`](../options/output.md#darkhighlighttheme) options. +TypeDoc only loads some of the languages supported by Shiki by default. If you +want to load additional languages, use the +[`highlightLanguages`](../options/output.md#highlightlanguages) option. ````ts /** @@ -76,6 +79,54 @@ Will be rendered as: > function example(): void; > ``` +## Comment Discovery + +In most cases, TypeDoc's comment discovery closely mirrors TypeScript's discovery. If a comment is placed +directly before a declaration or typically belongs to a declaration but lives on a parent node, TypeDoc +will include it in the documentation. + +```ts +/** + * This works + * @param x this works + */ +function example(x: string, /** This too */ y: number) {} +/** This also works */ +class Example2 {} +``` + +TypeDoc also supports discovering comments in some locations which TypeScript does not. + +1. Comments on type aliases directly containing unions may have comments before each union branch + to document the union. + + ```ts + type Choices = + /** Comment for option 1 */ + | "option_1" + /** Comment for option 2 */ + | { option_1: number }; + ``` + +2. Comments on export specifiers which export (or re-export) a member. + + ```ts + /** A comment here will take precedence over a module comment in Lib */ + export * as Lib from "lib"; + ``` + + Comments on export specifiers only have higher priority than the module comment for modules + and references where the symbol appears in the documentation multiple times. + + ```ts + export * as Lib from "lib"; // Use the @module comment + /** Preserved for backwards compatibility, prefer {@link Lib} */ + export * as Library from "lib"; + + /** This comment will be used for someFunction only if someFunction does not have a comment directly on it */ + export { someFunction } from "lib"; + ``` + ## See Also - The [Tags overview](../tags.md) diff --git a/site/options.md b/site/options.md index 8d39b3642..b5b3b9782 100644 --- a/site/options.md +++ b/site/options.md @@ -35,7 +35,7 @@ Options which control TypeDoc's HTML output. ## Comment Options -Options which control how TypeDoc parses comments. +Options which control how TypeDoc parses comments and documents. {@listOptions options/comments.md} diff --git a/site/options/comments.md b/site/options/comments.md index 4e7caea52..c90abd7f2 100644 --- a/site/options/comments.md +++ b/site/options/comments.md @@ -190,7 +190,7 @@ Note that `@deprecated` is a block tag, not a modifier tag, so should not be spe ## excludeTags ```bash -$ typedoc --excludeTags apidefine +$ typedoc --excludeTags @apidefine ``` Specify tags that should be removed from doc comments when parsing. @@ -199,7 +199,7 @@ Useful if your project uses [apiDoc](https://apidocjs.com/) for documenting REST ## notRenderedTags ```bash -$ typedoc --notRenderedTags beta +$ typedoc --notRenderedTags @beta ``` Specify tags which should be preserved in the doc comments, but not rendered @@ -207,6 +207,18 @@ when creating output. This is intended to support tags which carry some meaning about how to render a member or instructions for TypeDoc to do something after a package has been deserialized from JSON in packages mode. +## preservedTypeAnnotationTags + +```json +// typedoc.json +{ + "preservedTypeAnnotationTags": ["@fires"] +} +``` + +Specify block tags whose type annotations should be preserved by TypeDoc's parser, +leading to their content being included in the rendered documentation. + ## externalSymbolLinkMappings ```json diff --git a/site/options/configuration.md b/site/options/configuration.md index 74c5da8fa..a96aa0859 100644 --- a/site/options/configuration.md +++ b/site/options/configuration.md @@ -106,3 +106,6 @@ typedoc --plugin ./custom-plugin.js Specifies the plugins that should be loaded. By default, no plugins are loaded. See [Plugins](../plugins.md) for a list of available plugins. + +If using a JavaScript configuration file, the `plugin` option may be given +a function which will be called to load a plugin. diff --git a/site/options/input.md b/site/options/input.md index e7bdaf311..cc470cc49 100644 --- a/site/options/input.md +++ b/site/options/input.md @@ -35,7 +35,7 @@ If a `"typedoc"` [conditional export](https://nodejs.org/api/packages.html#condi TypeDoc will use it instead of the `"import"` export condition. The set of entry points provided to TypeDoc determines the names displayed in the documentation. -By default, TypeDoc will derive a [basePath](output.md#basepath) based on your entry point +By default, TypeDoc will derive a [displayBasePath](output.md#displaybasepath) based on your entry point paths to determine the displayed module name, but it can be also be set with the [`@module`](../tags/module.md) tag. ## entryPointStrategy @@ -215,7 +215,16 @@ Removes symbols annotated with the `@internal` doc tag. Defaults to true if the typedoc --excludePrivate ``` -Removes private class members from the generated documentation. Defaults to true. +Removes members marked with `private` and `#private` class fields from the generated documentation. Defaults to true. +To include `#private` class fields both this option and [excludePrivateClassFields](#excludeprivateclassfields) must be set to `false`. + +## excludePrivateClassFields + +```bash +typedoc --excludePrivateClassFields +``` + +Removes `#private` class fields from the generated documentation. Defaults to true. ## excludeProtected @@ -311,7 +320,8 @@ If you are updating documentation for a forked package, you probably want to pas typedoc --disableGit ``` -Prevents TypeDoc from using Git to try to determine if sources can be linked, with this enabled, sources will always be linked, even if not part of a git repo. +Prevents TypeDoc from using Git to try to determine if sources can be linked, with this enabled, sources will always be +linked, even if not part of a git repo. ## readme @@ -319,6 +329,15 @@ Prevents TypeDoc from using Git to try to determine if sources can be linked, wi typedoc --readme ``` -Path to the readme file that should be displayed on the index page. If set to -`none`, or no readme file is automatically discovered, the index page will be -disabled. +Path to the readme file that should be displayed on the index page. If set to `none`, or no readme file is automatically +discovered, the index page will be disabled. + +## basePath + +```bash +typedoc --basePath ./ +``` + +Path to a directory containing asset files which will be checked when resolving relative paths of links and images +within documentation comments and external documents. If specified, this will also be used for the default value of +the [displayBasePath](output.md#displaybasepath) option. diff --git a/site/options/organization.md b/site/options/organization.md index 155b9cd67..68e673b94 100644 --- a/site/options/organization.md +++ b/site/options/organization.md @@ -81,6 +81,9 @@ Specifies the sort order for members. Sorting strategies will be applied in orde If an earlier sorting strategy determines the relative ordering of two reflections, later ordering strategies will not be applied. +This option defines the default sort strategy, the [`@sortStrategy`](../tags/sortStrategy.md) +tag may be used to override it for individual reflections. + For example, with the setting `["static-first", "visibility"]`, TypeDoc will first compare two reflections by if they are static or not, and if that comparison returns equal, will check the visibility of each reflection. On the other hand, if `["visibility", "static-first"]` is specified, @@ -104,6 +107,18 @@ The available sorting strategies are: - `documents-last` - `alphabetical-ignoring-documents` +The default sort order is: + +```json +{ + "sort": [ + "kind", + "instance-first", + "alphabetical-ignoring-documents" + ] +} +``` + ## sortEntryPoints ```bash diff --git a/site/options/output.md b/site/options/output.md index ab859eb87..fbd6df90a 100644 --- a/site/options/output.md +++ b/site/options/output.md @@ -235,7 +235,7 @@ loads the following languages. ## ignoredHighlightLanguages Specifies languages used in code blocks which should be silently ignored by TypeDoc. -By default, TypeDoc will produce an error if a code block specifies a language which +By default, TypeDoc will produce a warning if a code block specifies a language which is not present in the highlightLanguages array. ```json @@ -318,14 +318,15 @@ export default { }; ``` -## basePath +## displayBasePath ```bash -$ typedoc --basePath ./ --entryPoints src/index.ts +$ typedoc --displayBasePath ./ --entryPoints src/index.ts ``` Specifies the base path to be used when displaying file paths. If not set, TypeDoc will guess by taking the lowest -common directory to all source files. In the above example, TypeDoc would display links to `index.ts` rather than `src/index.ts`. +common directory to all source files. In the above example, TypeDoc would display links to `index.ts` rather than `src/index.ts` +if `displayBasePath` was not specified. Defaults to the value of [basePath](input.md#basepath) > [!note] > This option only affects displayed paths. It _does not_ affect where TypeDoc will create links to. diff --git a/site/options/package-options.md b/site/options/package-options.md index ea055071f..1f90f4c0f 100644 --- a/site/options/package-options.md +++ b/site/options/package-options.md @@ -41,6 +41,7 @@ at the root level. The following tables indicate where an option should be set. | [`excludeNotDocumentedKinds`](input.md#excludenotdocumentedkinds) | Package | | | [`excludeInternal`](input.md#excludeinternal) | Package | | | [`excludePrivate`](input.md#excludeprivate) | Package | | +| [`excludePrivateClassFields`](input.md#excludeprivateclassfields) | Package | | | [`excludeProtected`](input.md#excludeprotected) | Package | | | [`excludeReferences`](input.md#excludereferences) | Package | | | [`excludeCategories`](input.md#excludecategories) | Package | | @@ -53,6 +54,7 @@ at the root level. The following tables indicate where an option should be set. | [`gitRemote`](input.md#gitremote) | Package | | | [`disableGit`](input.md#disablegit) | Package | | | [`readme`](input.md#readme) | Both | Root: Site readme, Package: Package readme | +| [`basePath`](input.md#basepath) | Both | Root: Site readme, documents, Package: Package readme, documentation comments, documents | ## Output Options @@ -77,7 +79,7 @@ at the root level. The following tables indicate where an option should be set. | [`customFooterHtmlDisableWrapper`](output.md#customfooterhtmldisablewrapper) | Root | | | [`markdownItOptions`](output.md#markdownitoptions) | Root | | | [`markdownItLoader`](output.md#markdownitloader) | Root | | -| [`basePath`](output.md#basepath) | Both | Used to determine file names of entry points and documents | +| [`displayBasePath`](output.md#displaybasepath) | Both | Used to determine file names of entry points and documents | | [`cname`](output.md#cname) | Root | | | [`favicon`](output.md#favicon) | Root | | | [`sourceLinkExternal`](output.md#sourcelinkexternal) | Root | | @@ -120,6 +122,7 @@ at the root level. The following tables indicate where an option should be set. | [`cascadedModifierTags`](comments.md#cascadedmodifiertags) | Package | | | [`excludeTags`](comments.md#excludetags) | Package | | | [`notRenderedTags`](comments.md#notrenderedtags) | Root | | +| [`preservedTypeAnnotationTags`](comments.md#preservedtypeannotationtags) | Package | | | [`externalSymbolLinkMappings`](comments.md#externalsymbollinkmappings) | Both | Unresolved links are checked both when converting and when merging projects | ## Organization Options diff --git a/site/overview.md b/site/overview.md index b5c7b4145..43db24a8a 100644 --- a/site/overview.md +++ b/site/overview.md @@ -62,7 +62,7 @@ const project = await app.convert(); if (project) { // Generate configured outputs - await generateOutputs(project); + await app.generateOutputs(project); // Alternatively... const outputDir = "docs"; diff --git a/site/tags.md b/site/tags.md index 1ff05824e..db8e485fb 100644 --- a/site/tags.md +++ b/site/tags.md @@ -47,8 +47,9 @@ children: - tags/remarks.md - tags/returns.md - tags/sealed.md - - tags/since.md - tags/see.md + - tags/since.md + - tags/sortStrategy.md - tags/summary.md - tags/template.md - tags/throws.md @@ -121,7 +122,7 @@ examples for how to use the export ([`@example`](./tags/example.md)). - [`@license`](./tags/license.md) - [`@mergeModuleWith`](./tags/mergeModuleWith.md) - [`@module`](./tags/module.md) -- [`@param`](./tags/param.md) +- [`@param`, `@this`](./tags/param.md) - [`@preventExpand`](./tags/expand.md#preventexpand) - [`@preventInline`](./tags/inline.md#preventinline) - [`@privateRemarks`](./tags/privateRemarks.md) @@ -130,6 +131,7 @@ examples for how to use the export ([`@example`](./tags/example.md)). - [`@returns`, `@return`](./tags/returns.md) - [`@see`](./tags/see.md) - [`@since`](./tags/since.md) +- [`@sortStrategy`](./tags/sortStrategy.md) - [`@summary`](./tags/summary.md) - [`@template`](./tags/template.md) - [`@throws`](./tags/throws.md) diff --git a/site/tags/packageDocumentation.md b/site/tags/packageDocumentation.md index eac3d3690..f1e54c5d6 100644 --- a/site/tags/packageDocumentation.md +++ b/site/tags/packageDocumentation.md @@ -5,7 +5,7 @@ title: "@packageDocumentation" # @packageDocumentation **Tag Kind:** [Modifier](../tags.md#modifier-tags)
-**TSDoc Reference:** [@packageDocumentation](https://tsdoc.org/pages/tags/packageDocumentation/) +**TSDoc Reference:** [@packageDocumentation](https://tsdoc.org/pages/tags/packagedocumentation/) The `@packageDocumentation` tag is used to mark a comment as referring to a file rather than the declaration following it. The TypeDoc specific [`@module`](module.md) tag can be used for the same purpose when semantically clearer. diff --git a/site/tags/param.md b/site/tags/param.md index 1a074f162..d98216e9b 100644 --- a/site/tags/param.md +++ b/site/tags/param.md @@ -2,7 +2,7 @@ title: "@param" --- -# @param +# @param and @this **Tag Kind:** [Block](../tags.md#block-tags)
**TSDoc Reference:** [@param](https://tsdoc.org/pages/tags/param/) @@ -54,6 +54,21 @@ export function configure({ value }: { value: string }) {} export function configure(options: { value: string }) {} ``` +## `this` Parameters + +Functions which use `this` in JavaScript files may use TypeScript's `@this` tag to define the type of their `this` +parameter. TypeDoc will check for `@this` tags and use their content in the description of the `this` parameter. + +```js +/** + * @this {Request} parameter description for `this` + * @param {Response} response parameter description for `response` + */ +export function hello(response) { + response.write(`Hello ${this.query.name || "world!"}`); +} +``` + ## TSDoc Compatibility The TSDoc standard requires that the `@param` tag _not_ include types and that diff --git a/site/tags/primaryExport.md b/site/tags/primaryExport.md index ab3533632..c1a5b13f4 100644 --- a/site/tags/primaryExport.md +++ b/site/tags/primaryExport.md @@ -1,8 +1,8 @@ --- -title: "@primaryModifier" +title: "@primaryExport" --- -# @primaryModifier +# @primaryExport **Tag Kind:** [Modifier](../tags.md#modifier-tags) diff --git a/site/tags/sortStrategy.md b/site/tags/sortStrategy.md new file mode 100644 index 000000000..c795c08e0 --- /dev/null +++ b/site/tags/sortStrategy.md @@ -0,0 +1,34 @@ +--- +title: "@sortStrategy" +--- + +# @sortStrategy + +**Tag Kind:** [Block](../tags.md#block-tags)
+ +This tag can be used to override the [sort](../options/organization.md#sort) locally +for a module, namespace, class, or interface. The override will be applied to direct +children of the declaration it appears on. If the declaration has a child which contains +children (e.g. a nested namespace) the grandchildren will _not_ be sorted according +to the `@sortStrategy` tag. + +## Example + +This class makes the most sense if the documentation is reviewed in the source order +rather than being sorted alphabetically. + +```ts +/** + * @sortStrategy source-order + */ +export class Class { + commonMethod(): void; + commonMethod2(): void; + lessCommonMethod(): void; + uncommonMethod(): void; +} +``` + +## See Also + +- The [`--sort`](../options/organization.md#sort) option diff --git a/site/typedoc.config.jsonc b/site/typedoc.config.jsonc index 48f0b91ae..ab9eca333 100644 --- a/site/typedoc.config.jsonc +++ b/site/typedoc.config.jsonc @@ -1,6 +1,7 @@ { "$schema": "https://typedoc.org/schema.json", "logLevel": "Verbose", + "treatWarningsAsErrors": true, "entryPointStrategy": "merge", "entryPoints": [], diff --git a/src/index.ts b/src/index.ts index 88fb52d87..aefdf11bc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -139,7 +139,9 @@ export { MinimalSourceFile, type NormalizedPath, type NormalizedPathOrModule, + type NormalizedPathOrModuleOrFunction, type SymbolReference, + type TagString, type TranslatedString, translateTagName, } from "#utils"; diff --git a/src/lib/application.ts b/src/lib/application.ts index 779b3310a..75b5cd214 100644 --- a/src/lib/application.ts +++ b/src/lib/application.ts @@ -43,7 +43,6 @@ import { Outputs } from "./output/output.js"; import { validateMergeModuleWith } from "./validation/unusedMergeModuleWith.js"; import { diagnostic, diagnostics } from "./utils/loggers.js"; import { ValidatingFileRegistry } from "./utils/ValidatingFileRegistry.js"; -import { addInferredDeclarationMapPaths } from "./converter/factories/symbol-id.js"; import { Internationalization } from "./internationalization/internationalization.js"; const packageInfo = JSON.parse( @@ -149,6 +148,10 @@ export class Application extends AbstractComponent< options = new Options(); + /** + * Due for deprecation in 0.29, use the reference to this on {@link ProjectReflection}, + * this was the wrong place for this member to live. + */ files: FileRegistry = new ValidatingFileRegistry(); /** @internal */ @@ -274,6 +277,10 @@ export class Application extends AbstractComponent< this.logger.level = this.options.getValue("logLevel"); } + if (this.files instanceof ValidatingFileRegistry) { + this.files.basePath = this.options.getValue("basePath"); + } + for ( const [lang, locales] of Object.entries( this.options.getValue("locales"), @@ -497,7 +504,7 @@ export class Application extends AbstractComponent< ); } - if (Object.keys(this.options.getCompilerOptions()).length === 0) { + if (Object.keys(this.options.getCompilerOptions(this.logger)).length === 0) { this.logger.warn(i18n.no_compiler_options_set()); } @@ -526,7 +533,7 @@ export class Application extends AbstractComponent< const host = ts.createWatchCompilerHost( tsconfigFile, - this.options.fixCompilerOptions({}), + this.options.fixCompilerOptions({}, this.logger), ts.sys, ts.createEmitAndSemanticDiagnosticsBuilderProgram, (d) => diagnostic(this.logger, d), @@ -822,18 +829,13 @@ export class Application extends AbstractComponent< continue; } - addInferredDeclarationMapPaths( - opts.getCompilerOptions(), - opts.getFileNames(), - ); - projectsToConvert.push({ dir, options: opts }); } for (const { dir, options } of projectsToConvert) { this.logger.info(i18n.converting_project_at_0(nicePath(dir))); this.options = options; - this.files = new ValidatingFileRegistry(); + this.files = new ValidatingFileRegistry(options.getValue("basePath")); let project = await this.convert(); if (project) { this.validate(project); diff --git a/src/lib/cli.ts b/src/lib/cli.ts index d45f4b006..75c3abf7e 100644 --- a/src/lib/cli.ts +++ b/src/lib/cli.ts @@ -26,7 +26,7 @@ async function main() { new td.TypeDocReader(), new td.PackageJsonReader(), new td.TSConfigReader(), - new td.ArgumentsReader(300), + new td.ArgumentsReader(300).ignoreErrors(), ]); const exitCode = await run(app); diff --git a/src/lib/converter/comments/blockLexer.ts b/src/lib/converter/comments/blockLexer.ts index 7bd0bb5af..dae38840f 100644 --- a/src/lib/converter/comments/blockLexer.ts +++ b/src/lib/converter/comments/blockLexer.ts @@ -1,12 +1,15 @@ import ts from "typescript"; import { type Token, TokenSyntaxKind } from "./lexer.js"; import { resolveAliasedSymbol } from "../utils/symbols.js"; -import { createSymbolId } from "../factories/symbol-id.js"; +import type { Context } from "../context.js"; export function* lexBlockComment( file: string, pos = 0, end = file.length, + createSymbolId: Context["createSymbolId"] = () => { + throw new Error("unreachable"); + }, jsDoc: ts.JSDoc | undefined = undefined, checker: ts.TypeChecker | undefined = undefined, ): Generator { @@ -19,6 +22,7 @@ export function* lexBlockComment( end, getLinkTags(jsDoc), checker, + createSymbolId, ) ) { if (token.kind === TokenSyntaxKind.Text) { @@ -82,6 +86,7 @@ function* lexBlockComment2( ts.JSDocLink | ts.JSDocLinkCode | ts.JSDocLinkPlain >, checker: ts.TypeChecker | undefined, + createSymbolId: Context["createSymbolId"], ): Generator { pos += 2; // Leading '/*' end -= 2; // Trailing '*/' @@ -263,7 +268,7 @@ function* lexBlockComment2( if (lookahead !== pos + 1) { while ( lookahead < end && - /[a-z0-9]/i.test(file[lookahead]) + /[a-z0-9-]/i.test(file[lookahead]) ) { lookahead++; } diff --git a/src/lib/converter/comments/discovery.ts b/src/lib/converter/comments/discovery.ts index 052d3a06f..719bf2c9e 100644 --- a/src/lib/converter/comments/discovery.ts +++ b/src/lib/converter/comments/discovery.ts @@ -4,6 +4,7 @@ import { CommentStyle } from "../../utils/options/declaration.js"; import { nicePath } from "../../utils/paths.js"; import { ok } from "assert"; import { assertNever, filter, firstDefined, i18n, type Logger } from "#utils"; +import { resolveAliasedSymbol } from "../utils/symbols.js"; const variablePropertyKinds = [ ts.SyntaxKind.PropertyDeclaration, @@ -80,6 +81,7 @@ const wantedKinds: Record = { [ReflectionKind.Interface]: [ ts.SyntaxKind.InterfaceDeclaration, ts.SyntaxKind.TypeAliasDeclaration, + ts.SyntaxKind.ClassDeclaration, // type only exports ], [ReflectionKind.Constructor]: [ts.SyntaxKind.Constructor], [ReflectionKind.Property]: variablePropertyKinds, @@ -104,7 +106,12 @@ const wantedKinds: Record = { [ReflectionKind.Accessor]: [ts.SyntaxKind.PropertyDeclaration], [ReflectionKind.GetSignature]: [ts.SyntaxKind.GetAccessor], [ReflectionKind.SetSignature]: [ts.SyntaxKind.SetAccessor], - [ReflectionKind.TypeAlias]: [ts.SyntaxKind.TypeAliasDeclaration], + [ReflectionKind.TypeAlias]: [ + ts.SyntaxKind.TypeAliasDeclaration, + ts.SyntaxKind.FunctionDeclaration, // type only exports + // Intentionally not included to avoid comments being copied for variable/alias combos + // ts.SyntaxKind.VariableDeclaration, + ], [ReflectionKind.Reference]: [ ts.SyntaxKind.NamespaceExport, ts.SyntaxKind.ExportSpecifier, @@ -351,15 +358,11 @@ function findJsDocForComment( if (ranges[0].kind === ts.SyntaxKind.MultiLineCommentTrivia) { const jsDocs = ts .getJSDocCommentsAndTags(node) - .map((doc) => ts.findAncestor(doc, ts.isJSDoc)) as ts.JSDoc[]; + .map((doc) => ts.findAncestor(doc, ts.isJSDoc)!); if (ts.isSourceFile(node)) { if (node.statements.length) { - jsDocs.push( - ...(ts - .getJSDocCommentsAndTags(node.statements[0]) - .map((doc) => ts.findAncestor(doc, ts.isJSDoc)) as ts.JSDoc[]), - ); + jsDocs.push(...node.statements[0].getChildren().filter(ts.isJSDoc)); } } @@ -523,6 +526,36 @@ function declarationToCommentNodes( }); } + // #2999 automatically pick up comments from the value symbol for shorthand assignments + if (ts.isShorthandPropertyAssignment(node)) { + const sourceSymbol = checker.getShorthandAssignmentValueSymbol(node); + if (sourceSymbol?.valueDeclaration) { + const commentNode = declarationToCommentNodeIgnoringParents(sourceSymbol.valueDeclaration); + if (commentNode) { + result.push( + { + node: commentNode, + inheritedFromParentDeclaration: true, + }, + ); + } + } + + // #3003 even more magic for handling an imported symbol which appears in a shorthand property assignment + const originalSymbol = sourceSymbol && resolveAliasedSymbol(sourceSymbol, checker); + if (originalSymbol !== sourceSymbol && originalSymbol?.valueDeclaration) { + const commentNode = declarationToCommentNodeIgnoringParents(originalSymbol?.valueDeclaration); + if (commentNode) { + result.push( + { + node: commentNode, + inheritedFromParentDeclaration: true, + }, + ); + } + } + } + // With overloaded functions/methods, TypeScript will use the comment on the first signature // declaration if ( diff --git a/src/lib/converter/comments/index.ts b/src/lib/converter/comments/index.ts index 5828e711a..faf890547 100644 --- a/src/lib/converter/comments/index.ts +++ b/src/lib/converter/comments/index.ts @@ -13,17 +13,35 @@ import { lexLineComments } from "./lineLexer.js"; import { parseComment } from "./parser.js"; import type { FileRegistry } from "../../models/FileRegistry.js"; import { assertNever, i18n, type Logger } from "#utils"; +import type { Context } from "../context.js"; export interface CommentParserConfig { blockTags: Set; inlineTags: Set; modifierTags: Set; + preservedTypeAnnotationTags: Set; jsDocCompatibility: JsDocCompatibility; suppressCommentWarningsInDeclarationFiles: boolean; useTsLinkResolution: boolean; commentStyle: CommentStyle; } +export interface CommentContext { + config: CommentParserConfig; + logger: Logger; + checker: ts.TypeChecker; + files: FileRegistry; + createSymbolId: Context["createSymbolId"]; +} + +export interface CommentContextOptionalChecker { + config: CommentParserConfig; + logger: Logger; + checker?: ts.TypeChecker | undefined; + files: FileRegistry; + createSymbolId: Context["createSymbolId"]; +} + const jsDocCommentKinds = [ ts.SyntaxKind.JSDocPropertyTag, ts.SyntaxKind.JSDocCallbackTag, @@ -45,10 +63,7 @@ export function clearCommentCache() { function getCommentWithCache( discovered: DiscoveredComment | undefined, - config: CommentParserConfig, - logger: Logger, - checker: ts.TypeChecker | undefined, - files: FileRegistry, + context: CommentContextOptionalChecker, ) { if (!discovered) return; @@ -68,22 +83,19 @@ function getCommentWithCache( file.text, ranges[0].pos, ranges[0].end, + context.createSymbolId, jsDoc, - checker, + context.checker, ), - config, file, - logger, - files, + context, ); break; case ts.SyntaxKind.SingleLineCommentTrivia: comment = parseComment( lexLineComments(file.text, ranges), - config, file, - logger, - files, + context, ); break; default: @@ -100,18 +112,15 @@ function getCommentWithCache( function getCommentImpl( commentSource: DiscoveredComment | undefined, - config: CommentParserConfig, - logger: Logger, moduleComment: boolean, - checker: ts.TypeChecker | undefined, - files: FileRegistry, + context: CommentContext, ) { const comment = getCommentWithCache( commentSource, - config, - logger, - config.useTsLinkResolution ? checker : undefined, - files, + { + ...context, + checker: context.config.useTsLinkResolution ? context.checker : undefined, + }, ); if (comment?.getTag("@import") || comment?.getTag("@license")) { @@ -145,10 +154,7 @@ function getCommentImpl( export function getComment( symbol: ts.Symbol, kind: ReflectionKind, - config: CommentParserConfig, - logger: Logger, - checker: ts.TypeChecker, - files: FileRegistry, + context: CommentContext, ): Comment | undefined { const declarations = symbol.declarations || []; @@ -158,16 +164,13 @@ export function getComment( ) { return getJsDocComment( declarations[0] as ts.JSDocPropertyLikeTag, - config, - logger, - checker, - files, + context, ); } const sf = declarations.find(ts.isSourceFile); if (sf) { - return getFileComment(sf, config, logger, checker, files); + return getFileComment(sf, context); } const isModule = declarations.some((decl) => { @@ -181,25 +184,19 @@ export function getComment( discoverComment( symbol, kind, - logger, - config.commentStyle, - checker, - !config.suppressCommentWarningsInDeclarationFiles, + context.logger, + context.config.commentStyle, + context.checker, + !context.config.suppressCommentWarningsInDeclarationFiles, ), - config, - logger, isModule, - checker, - files, + context, ); if (!comment && kind === ReflectionKind.Property) { return getConstructorParamPropertyComment( symbol, - config, - logger, - checker, - files, + context, ); } @@ -209,40 +206,28 @@ export function getComment( export function getNodeComment( node: ts.Node, moduleComment: boolean, - config: CommentParserConfig, - logger: Logger, - checker: ts.TypeChecker | undefined, - files: FileRegistry, + context: CommentContext, ) { return getCommentImpl( - discoverNodeComment(node, config.commentStyle), - config, - logger, + discoverNodeComment(node, context.config.commentStyle), moduleComment, - checker, - files, + context, ); } export function getFileComment( file: ts.SourceFile, - config: CommentParserConfig, - logger: Logger, - checker: ts.TypeChecker | undefined, - files: FileRegistry, + context: CommentContext, ): Comment | undefined { for ( const commentSource of discoverFileComments( file, - config.commentStyle, + context.config.commentStyle, ) ) { const comment = getCommentWithCache( commentSource, - config, - logger, - config.useTsLinkResolution ? checker : undefined, - files, + context, ); if (comment?.getTag("@license") || comment?.getTag("@import")) { @@ -261,16 +246,13 @@ export function getFileComment( function getConstructorParamPropertyComment( symbol: ts.Symbol, - config: CommentParserConfig, - logger: Logger, - checker: ts.TypeChecker, - files: FileRegistry, + context: CommentContext, ): Comment | undefined { const decl = symbol.declarations?.find(ts.isParameter); if (!decl) return; const ctor = decl.parent; - const comment = getSignatureComment(ctor, config, logger, checker, files); + const comment = getSignatureComment(ctor, context); const paramTag = comment?.getIdentifiedTag(symbol.name, "@param"); if (paramTag) { @@ -282,18 +264,12 @@ function getConstructorParamPropertyComment( export function getSignatureComment( declaration: ts.SignatureDeclaration | ts.JSDocSignature, - config: CommentParserConfig, - logger: Logger, - checker: ts.TypeChecker, - files: FileRegistry, + context: CommentContext, ): Comment | undefined { return getCommentImpl( - discoverSignatureComment(declaration, checker, config.commentStyle), - config, - logger, + discoverSignatureComment(declaration, context.checker, context.config.commentStyle), false, - checker, - files, + context, ); } @@ -304,10 +280,7 @@ export function getJsDocComment( | ts.JSDocTypedefTag | ts.JSDocTemplateTag | ts.JSDocEnumTag, - config: CommentParserConfig, - logger: Logger, - checker: ts.TypeChecker | undefined, - files: FileRegistry, + context: CommentContext, ): Comment | undefined { const file = declaration.getSourceFile(); @@ -331,10 +304,7 @@ export function getJsDocComment( jsDoc: parent, inheritedFromParentDeclaration: false, }, - config, - logger, - config.useTsLinkResolution ? checker : undefined, - files, + context, )!; // And pull out the tag we actually care about. @@ -352,7 +322,7 @@ export function getJsDocComment( // We could just put the same comment on everything, but due to how comment parsing works, // we'd have to search for any @template with a name starting with the first type parameter's name // which feels horribly hacky. - logger.warn( + context.logger.warn( i18n.multiple_type_parameters_on_template_tag_unsupported(), declaration, ); @@ -378,7 +348,7 @@ export function getJsDocComment( // was a comment attached. If there wasn't, then don't error about failing to find // a tag because this is unsupported. if (!ts.isJSDocTemplateTag(declaration)) { - logger.error( + context.logger.error( i18n.failed_to_find_jsdoc_tag_for_name_0(name), declaration, ); diff --git a/src/lib/converter/comments/lineLexer.ts b/src/lib/converter/comments/lineLexer.ts index b7312f433..876494d77 100644 --- a/src/lib/converter/comments/lineLexer.ts +++ b/src/lib/converter/comments/lineLexer.ts @@ -179,7 +179,7 @@ function* lexLineComments2( if (lookahead !== pos + 1) { while ( lookahead < end && - /[a-z0-9]/i.test(file[lookahead]) + /[a-z0-9-]/i.test(file[lookahead]) ) { lookahead++; } diff --git a/src/lib/converter/comments/linkResolver.ts b/src/lib/converter/comments/linkResolver.ts index 4bcfc1014..45a0f9482 100644 --- a/src/lib/converter/comments/linkResolver.ts +++ b/src/lib/converter/comments/linkResolver.ts @@ -1,9 +1,8 @@ import ts from "typescript"; import { - type Comment, type CommentDisplayPart, - DeclarationReflection, type InlineTagDisplayPart, + makeRecursiveVisitor, Reflection, ReflectionKind, ReflectionSymbolId, @@ -35,27 +34,28 @@ export type LinkResolverOptions = { }; export function resolveLinks( - comment: Comment, reflection: Reflection, externalResolver: ExternalSymbolResolver, options: LinkResolverOptions, ) { - comment.summary = resolvePartLinks( - reflection, - comment.summary, - externalResolver, - options, - ); - for (const tag of comment.blockTags) { - tag.content = resolvePartLinks( + if (reflection.comment) { + reflection.comment.summary = resolvePartLinks( reflection, - tag.content, + reflection.comment.summary, externalResolver, options, ); + for (const tag of reflection.comment.blockTags) { + tag.content = resolvePartLinks( + reflection, + tag.content, + externalResolver, + options, + ); + } } - if (reflection instanceof DeclarationReflection && reflection.readme) { + if ((reflection.isDeclaration() || reflection.isProject()) && reflection.readme) { reflection.readme = resolvePartLinks( reflection, reflection.readme, @@ -64,6 +64,18 @@ export function resolveLinks( ); } + if (reflection.isDeclaration()) { + reflection.type?.visit( + makeRecursiveVisitor({ + union: (type) => { + type.elementSummaries = type.elementSummaries?.map( + (parts) => resolvePartLinks(reflection, parts, externalResolver, options), + ); + }, + }), + ); + } + if (reflection.isDocument()) { reflection.content = resolvePartLinks( reflection, @@ -72,6 +84,57 @@ export function resolveLinks( options, ); } + + if ( + reflection.isParameter() && + reflection.type?.type === "reference" && + reflection.type.highlightedProperties + ) { + const resolved = new Map( + Array.from( + reflection.type.highlightedProperties, + ([name, parts]) => { + return [ + name, + resolvePartLinks(reflection, parts, externalResolver, options), + ]; + }, + ), + ); + + reflection.type.highlightedProperties = resolved; + } + + if (reflection.isContainer()) { + if (reflection.groups) { + for (const group of reflection.groups) { + if (group.description) { + group.description = resolvePartLinks( + reflection, + group.description, + externalResolver, + options, + ); + } + + if (group.categories) { + for (const cat of group.categories) { + if (cat.description) { + cat.description = resolvePartLinks(reflection, cat.description, externalResolver, options); + } + } + } + } + } + + if (reflection.categories) { + for (const cat of reflection.categories) { + if (cat.description) { + cat.description = resolvePartLinks(reflection, cat.description, externalResolver, options); + } + } + } + } } export function resolvePartLinks( @@ -133,10 +196,10 @@ function resolveLinkTag( if (tsTargets.length) { // Find the target most likely to have a real url in the generated documentation - // 1. A direct export (class, interface, variable) - // 2. A property of a direct export (class/interface property) - // 3. A property of a type of an export (property on type alias) - // 4. Whatever the first symbol found was + // 4. A direct export (class, interface, variable) + // 3. A property of a direct export (class/interface property) + // 2. A property of a type of an export (property on type alias) + // 1. Whatever the first symbol found was target = maxElementByScore(tsTargets, (r) => { if (r.kindOf(ReflectionKind.SomeExport)) { return 4; diff --git a/src/lib/converter/comments/parser.ts b/src/lib/converter/comments/parser.ts index b95210555..4453666c6 100644 --- a/src/lib/converter/comments/parser.ts +++ b/src/lib/converter/comments/parser.ts @@ -1,8 +1,8 @@ import assert, { ok } from "assert"; import { parseDocument as parseYamlDoc } from "yaml"; -import type { CommentParserConfig } from "./index.js"; +import type { CommentContextOptionalChecker, CommentParserConfig } from "./index.js"; import { Comment, type CommentDisplayPart, CommentTag, type InlineTagDisplayPart } from "../../models/index.js"; -import type { MinimalSourceFile } from "#utils"; +import type { MinimalSourceFile, TagString } from "#utils"; import { nicePath } from "../../utils/paths.js"; import { type Token, TokenSyntaxKind } from "./lexer.js"; import { extractTagName } from "./tagName.js"; @@ -62,10 +62,8 @@ function makeLookaheadGenerator( export function parseComment( tokens: Generator, - config: CommentParserConfig, file: MinimalSourceFile, - logger: Logger, - files: FileRegistry, + context: CommentContextOptionalChecker, ): Comment { const lexer = makeLookaheadGenerator(tokens); const tok = lexer.done() || lexer.peek(); @@ -75,15 +73,15 @@ export function parseComment( comment.summary = blockContent( comment, lexer, - config, + context.config, i18n, warningImpl, - files, + context.files, ); while (!lexer.done()) { comment.blockTags.push( - blockTag(comment, lexer, config, i18n, warningImpl, files), + blockTag(comment, lexer, context.config, i18n, warningImpl, context.files), ); } @@ -93,19 +91,19 @@ export function parseComment( comment, i18n, () => `${nicePath(file.fileName)}:${file.getLineAndCharacterOfPosition(tok2.pos).line + 1}`, - (message) => logger.warn(message), + (message) => context.logger.warn(message), ); return comment; function warningImpl(message: TranslatedString, token: Token) { if ( - config.suppressCommentWarningsInDeclarationFiles && + context.config.suppressCommentWarningsInDeclarationFiles && hasDeclarationFileExtension(file.fileName) ) { return; } - logger.warn(message, token.pos, file); + context.logger.warn(message, token.pos, file); } } @@ -237,7 +235,7 @@ export function parseCommentString( return { content, frontmatter: frontmatterData }; } -const HAS_USER_IDENTIFIER: `@${string}`[] = [ +const HAS_USER_IDENTIFIER: TagString[] = [ "@callback", "@param", "@prop", @@ -348,6 +346,13 @@ function postProcessComment( ), ); } + if ((inlineInheritDoc.length || inheritDoc.length) && returns.length) { + warning( + i18n.content_in_returns_block_overwritten_by_inheritdoc_in_comment_at_0( + getPosition(), + ), + ); + } } const aliasedTags = new Map([["@return", "@returns"]]); @@ -375,7 +380,22 @@ function blockTag( let content: CommentDisplayPart[]; if (tagName === "@example") { return exampleBlock(comment, lexer, config, i18n, warning, files); - } else if ( + } + + let typeAnnotation: string | undefined; + if ( + !lexer.done() && + config.preservedTypeAnnotationTags.has(tagName) + ) { + if (lexer.peek().kind === TokenSyntaxKind.Text && /^\s+$/.test(lexer.peek().text)) { + lexer.take(); + } + if (lexer.peek().kind === TokenSyntaxKind.TypeAnnotation) { + typeAnnotation = lexer.take().text; + } + } + + if ( ["@default", "@defaultValue"].includes(tagName) && config.jsDocCompatibility.defaultTag ) { @@ -391,7 +411,11 @@ function blockTag( content = blockContent(comment, lexer, config, i18n, warning, files); } - return new CommentTag(tagName as `@${string}`, content); + const tag = new CommentTag(tagName as TagString, content); + if (typeAnnotation) { + tag.typeAnnotation = typeAnnotation; + } + return tag; } /** @@ -615,11 +639,11 @@ function blockContent( next.text = "@inheritDoc"; } if (config.modifierTags.has(next.text)) { - comment.modifierTags.add(next.text as `@${string}`); + comment.modifierTags.add(next.text as TagString); break; } else if (!atNewLine && !config.blockTags.has(next.text)) { // Treat unknown tag as a modifier, but warn about it. - comment.modifierTags.add(next.text as `@${string}`); + comment.modifierTags.add(next.text as TagString); warning( i18n.treating_unrecognized_tag_0_as_modifier(next.text), next, @@ -753,7 +777,7 @@ function inlineTag( const inlineTag: InlineTagDisplayPart = { kind: "inline-tag", - tag: tagName.text as `@${string}`, + tag: tagName.text as TagString, text: content.join(""), }; if (tagName.tsLinkTarget) { diff --git a/src/lib/converter/comments/rawLexer.ts b/src/lib/converter/comments/rawLexer.ts index e8bc8d6bc..ff063dc11 100644 --- a/src/lib/converter/comments/rawLexer.ts +++ b/src/lib/converter/comments/rawLexer.ts @@ -176,7 +176,7 @@ function* lexCommentString2( if (lookahead !== pos + 1) { while ( lookahead < end && - /[a-z0-9]/i.test(file[lookahead]) + /[a-z0-9-]/i.test(file[lookahead]) ) { lookahead++; } diff --git a/src/lib/converter/comments/textParser.ts b/src/lib/converter/comments/textParser.ts index 1c020a201..e98f18fa9 100644 --- a/src/lib/converter/comments/textParser.ts +++ b/src/lib/converter/comments/textParser.ts @@ -7,7 +7,7 @@ */ import type { TranslationProxy } from "../../internationalization/index.js"; import type { CommentDisplayPart, RelativeLinkDisplayPart } from "../../models/index.js"; -import type { FileRegistry } from "../../models/FileRegistry.js"; +import type { FileId, FileRegistry } from "../../models/FileRegistry.js"; import { HtmlAttributeParser, ParserState } from "#node-utils"; import { type Token, TokenSyntaxKind } from "./lexer.js"; @@ -19,7 +19,6 @@ interface TextParserData { sourcePath: NormalizedPath; token: Token; pos: number; - i18n: TranslationProxy; warning: (msg: TranslatedString, token: Token) => void; files: FileRegistry; atNewLine: boolean; @@ -29,7 +28,7 @@ interface RelativeLink { pos: number; end: number; /** May be undefined if the registry can't find this file */ - target: number | undefined; + target: FileId | undefined; targetAnchor: string | undefined; } @@ -41,6 +40,7 @@ interface RelativeLink { */ export class TextParserReentryState { withinLinkLabel = false; + withinLinkDest = false; private lastPartWasNewline = false; checkState(token: Token) { @@ -48,11 +48,13 @@ export class TextParserReentryState { case TokenSyntaxKind.Code: if (/\n\s*\n/.test(token.text)) { this.withinLinkLabel = false; + this.withinLinkDest = false; } break; case TokenSyntaxKind.NewLine: if (this.lastPartWasNewline) { this.withinLinkLabel = false; + this.withinLinkDest = false; } break; } @@ -76,17 +78,18 @@ export function textContent( reentry: TextParserReentryState, ) { let lastPartEnd = 0; + let canEndMarkdownLink = true; const data: TextParserData = { sourcePath, token, pos: 0, // relative to the token - i18n, warning, files: files, atNewLine, }; function addRef(ref: RelativeLink) { + canEndMarkdownLink = true; outContent.push({ kind: "text", text: token.text.slice(lastPartEnd, ref.pos), @@ -116,10 +119,15 @@ export function textContent( } while (data.pos < token.text.length) { - const link = checkMarkdownLink(data, reentry); - if (link) { - addRef(link); - continue; + if (canEndMarkdownLink) { + const link = checkMarkdownLink(data, reentry); + if (link) { + addRef(link); + continue; + } + // If we're within a Markdown link, then `checkMarkdownLink` + // already scanned `token` up to a line feed (if any). + canEndMarkdownLink = !reentry.withinLinkLabel && !reentry.withinLinkDest; } const reference = checkReference(data); @@ -128,13 +136,17 @@ export function textContent( continue; } - const tagLink = checkTagLink(data); - if (tagLink) { - addRef(tagLink); + const tagLinks = checkTagLink(data); + if (tagLinks.length) { + for (const tagLink of tagLinks) { + addRef(tagLink); + } continue; } - data.atNewLine = token.text[data.pos] === "\n"; + const atNewLine = token.text[data.pos] === "\n"; + data.atNewLine = atNewLine; + if (atNewLine && !reentry.withinLinkDest) canEndMarkdownLink = true; ++data.pos; } @@ -160,53 +172,74 @@ function checkMarkdownLink( const { token, sourcePath, files } = data; let searchStart: number; - if (reentry.withinLinkLabel) { + if (reentry.withinLinkLabel || reentry.withinLinkDest) { searchStart = data.pos; - reentry.withinLinkLabel = false; } else if (token.text[data.pos] === "[") { searchStart = data.pos + 1; } else { return; } - const labelEnd = findLabelEnd(token.text, searchStart); - if (labelEnd === -1) { - // This markdown link might be split across multiple display parts - // [ `text` ](link) - // ^^ text - // ^^^^^^ code - // ^^^^^^^^ text - reentry.withinLinkLabel = true; - return; + if (!reentry.withinLinkDest) { + const labelEnd = findLabelEnd(token.text, searchStart); + if (labelEnd === -1 || token.text[labelEnd] === "\n") { + // This markdown link might be split across multiple lines or input tokens + // [prefix `code` suffix](target) + // ........^^^^^^................ + // Unless we encounter two consecutive line feeds, expect it to keep going. + reentry.withinLinkLabel = labelEnd !== data.pos || !data.atNewLine; + return; + } + reentry.withinLinkLabel = false; + if (!token.text.startsWith("](", labelEnd)) return; + searchStart = labelEnd + 2; } - if (token.text[labelEnd] === "]" && token.text[labelEnd + 1] === "(") { - const link = MdHelpers.parseLinkDestination( - token.text, - labelEnd + 2, - token.text.length, - ); - - if (link.ok) { - // Only make a relative-link display part if it's actually a relative link. - // Discard protocol:// links, unix style absolute paths, and windows style absolute paths. - if (isRelativePath(link.str)) { - const { target, anchor } = files.register( - sourcePath, - link.str as NormalizedPath, - ) || { target: undefined, anchor: undefined }; - return { - pos: labelEnd + 2, - end: link.pos, - target, - targetAnchor: anchor, - }; - } - - // This was a link, skip ahead to ensure we don't happen to parse - // something else as a link within the link. - data.pos = link.pos - 1; + // Skip whitespace (including line breaks) between "](" and the link destination. + // https://spec.commonmark.org/0.31.2/#links + const end = token.text.length; + let lookahead = searchStart; + for (let newlines = 0;; ++lookahead) { + if (lookahead === end) { + reentry.withinLinkDest = true; + return; } + switch (token.text[lookahead]) { + case "\n": + if (++newlines === 2) { + reentry.withinLinkDest = false; + return; + } + continue; + case " ": + case "\t": + continue; + } + break; + } + reentry.withinLinkDest = false; + + const link = MdHelpers.parseLinkDestination(token.text, lookahead, end); + if (link.ok) { + // Only make a relative-link display part if it's actually a relative link. + // Discard protocol:// links, unix style absolute paths, and windows style absolute paths. + const decoded = decodeURI(link.str); + if (isRelativePath(decoded)) { + const { target, anchor } = files.register( + sourcePath, + decoded as NormalizedPath, + ) || { target: undefined, anchor: undefined }; + return { + pos: lookahead, + end: link.pos, + target, + targetAnchor: anchor, + }; + } + + // This was a link, skip ahead to ensure we don't happen to parse + // something else as a link within the link. + data.pos = link.pos - 1; } } @@ -226,7 +259,13 @@ function checkReference(data: TextParserData): RelativeLink | undefined { while (/[ \t]/.test(token.text[lookahead])) { ++lookahead; } - if (token.text[lookahead] === "[") { + // #2991, we check that this reference also doesn't look like a footnote reference + // as it is unlikely that someone uses that syntax without intending for footnote behavior. + // This introduces a problem if someone has an [^ref] and doesn't intend for that to + // be interpreted as a footnote, but as a reference, but we can't have it both ways, + // and having people rename their reference to not be confused with a footnote isn't a + // horrible workaround. + if (token.text[lookahead] === "[" && token.text[lookahead + 1] !== "^") { while ( lookahead < token.text.length && /[^\n\]]/.test(token.text[lookahead]) @@ -246,10 +285,11 @@ function checkReference(data: TextParserData): RelativeLink | undefined { ); if (link.ok) { - if (isRelativePath(link.str)) { + const decoded = decodeURI(link.str); + if (isRelativePath(decoded)) { const { target, anchor } = files.register( sourcePath, - link.str as NormalizedPath, + decoded as NormalizedPath, ) || { target: undefined, anchor: undefined }; return { pos: lookahead, @@ -267,52 +307,154 @@ function checkReference(data: TextParserData): RelativeLink | undefined { } /** - * Looks for `` and `` + * Looks for ``, ``, and `` */ -function checkTagLink(data: TextParserData): RelativeLink | undefined { +function checkTagLink(data: TextParserData): RelativeLink[] { const { pos, token } = data; if (token.text.startsWith(" RelativeLink[] + >, +): RelativeLink[] { + const links: RelativeLink[] = []; const parser = new HtmlAttributeParser(data.token.text, data.pos); while (parser.state !== ParserState.END) { if ( parser.state === ParserState.BeforeAttributeValue && - parser.currentAttributeName === attr + Object.prototype.hasOwnProperty.call(attributes, parser.currentAttributeName) ) { parser.step(); - if (isRelativePath(parser.currentAttributeValue)) { - data.pos = parser.pos; - const { target, anchor } = data.files.register( - data.sourcePath, - parser.currentAttributeValue as NormalizedPath, - ) || { target: undefined, anchor: undefined }; - return { - pos: parser.currentAttributeValueStart, - end: parser.currentAttributeValueEnd, - target, - targetAnchor: anchor, - }; - } - return; + links.push(...attributes[parser.currentAttributeName]( + data, + parser.currentAttributeValue, + parser.currentAttributeValueStart, + parser.currentAttributeValueEnd, + )); } parser.step(); } + + return links; +} + +function checkAttributeDirectPath( + data: TextParserData, + text: string, + pos: number, + end: number, +): RelativeLink[] { + const decoded = decodeURI(text.trim()); + if (isRelativePath(decoded)) { + const { target, anchor } = data.files.register( + data.sourcePath, + decoded as NormalizedPath, + ) || { target: undefined, anchor: undefined }; + return [{ + pos, + end, + target, + targetAnchor: anchor, + }]; + } + + return []; +} + +// See https://html.spec.whatwg.org/multipage/images.html#srcset-attribute +function checkAttributeSrcSet(data: TextParserData, text: string, pos: number, _end: number): RelativeLink[] { + const result: RelativeLink[] = []; + + let textPos = 0; + parseImageCandidate(); + while (textPos < text.length && text[textPos] == ",") { + ++textPos; + parseImageCandidate(); + } + + return result; + + function parseImageCandidate() { + // 1. Zero or more ASCII whitespace + while (textPos < text.length && /[\t\r\f\n ]/.test(text[textPos])) ++textPos; + // 2. A valid non-empty URL that does not start or end with a comma + // TypeDoc: We don't exactly match this, PR welcome! For now, just permit anything + // that's not whitespace or a comma + const url = text.slice(textPos).match(/^[^\t\r\f\n ,]+/); + const decoded = url && decodeURI(url[0]); + + if (decoded && isRelativePath(decoded)) { + const { target, anchor } = data.files.register( + data.sourcePath, + decoded as NormalizedPath, + ) || { target: undefined, anchor: undefined }; + result.push({ + pos: pos + textPos, + end: pos + textPos + url[0].length, + target, + targetAnchor: anchor, + }); + } + textPos += url ? url[0].length : 0; + + // 3. Zero or more ASCII whitespace + while (textPos < text.length && /[\t\r\f\n ]/.test(text[textPos])) ++textPos; + + // 4. Zero or one of the following: + { + // A width descriptor, consisting of: ASCII whitespace, a valid non-negative integer giving + // a number greater than zero representing the width descriptor value, and a U+0077 LATIN + // SMALL LETTER W character. + const w = text.slice(textPos).match(/^\+?\d+\s*w/); + textPos += w ? w[0].length : 0; + + // A pixel density descriptor, consisting of: ASCII whitespace, a valid floating-point number + // giving a number greater than zero representing the pixel density descriptor value, and a + // U+0078 LATIN SMALL LETTER X character. + if (!w) { + const x = text.slice(textPos).match(/^\+?\d+(\.\d+)?([eE][+-]\d+)?\s*x/); + textPos += x ? x[0].length : 0; + } + } + + // 5. Zero or more ASCII whitespace + while (textPos < text.length && /[\t\r\f\n ]/.test(text[textPos])) ++textPos; + } } function isRelativePath(link: string) { @@ -328,6 +470,10 @@ function isRelativePath(link: string) { function findLabelEnd(text: string, pos: number) { while (pos < text.length) { switch (text[pos]) { + case "\\": + ++pos; + if (pos < text.length && text[pos] === "\n") return pos; + break; case "\n": case "]": case "[": diff --git a/src/lib/converter/context.ts b/src/lib/converter/context.ts index f0a59f187..7f5697164 100644 --- a/src/lib/converter/context.ts +++ b/src/lib/converter/context.ts @@ -17,10 +17,17 @@ import type { Converter } from "./converter.js"; import { isNamedNode } from "./utils/nodes.js"; import { ConverterEvents } from "./converter-events.js"; import { resolveAliasedSymbol } from "./utils/symbols.js"; -import { getComment, getFileComment, getJsDocComment, getNodeComment, getSignatureComment } from "./comments/index.js"; +import { + type CommentContext, + getComment, + getFileComment, + getJsDocComment, + getNodeComment, + getSignatureComment, +} from "./comments/index.js"; import { getHumanName, getQualifiedName } from "../utils/tsutils.js"; import { findPackageForPath, normalizePath } from "#node-utils"; -import { createSymbolId } from "./factories/symbol-id.js"; +import { createSymbolIdImpl } from "./factories/symbol-id.js"; import { type NormalizedPath, removeIf } from "#utils"; /** @@ -189,17 +196,30 @@ export class Context { symbol: ts.Symbol | undefined, exportSymbol: ts.Symbol | undefined, ) { + // Allow comments on export declarations to take priority over comments directly + // on the symbol to enable overriding comments on modules/references, #1504 if ( + !reflection.comment && exportSymbol && - reflection.kind & - (ReflectionKind.SomeModule | ReflectionKind.Reference) + (reflection.kind & + (ReflectionKind.SomeModule | ReflectionKind.Reference)) ) { reflection.comment = this.getComment(exportSymbol, reflection.kind); } + + // If that didn't get us a comment (the normal case), then get the comment from + // the source declarations, this is the common case. if (symbol && !reflection.comment) { reflection.comment = this.getComment(symbol, reflection.kind); } + // If we still don't have a comment, check for any comments on the export declaration, + // we don't have to worry about functions being weird in this case as the regular declaration + // doesn't have any comment. + if (exportSymbol && !reflection.comment) { + reflection.comment = this.getComment(exportSymbol, ReflectionKind.Reference); + } + if (this.shouldBeStatic) { reflection.setFlag(ReflectionFlag.Static); } @@ -209,7 +229,7 @@ export class Context { this.addChild(reflection); } - if (symbol && this.converter.isExternal(symbol)) { + if (symbol && this.converter.isExternal(symbol, this.checker)) { reflection.setFlag(ReflectionFlag.External); } if (exportSymbol) { @@ -254,7 +274,7 @@ export class Context { ): ReferenceType { const ref = ReferenceType.createUnresolvedReference( name ?? symbol.name, - createSymbolId(symbol), + this.createSymbolId(symbol), context.project, getQualifiedName(symbol, name ?? symbol.name), ); @@ -269,6 +289,18 @@ export class Context { return ref; } + /** + * Create a stable {@link ReflectionSymbolId} for the provided symbol, + * optionally targeting a specific declaration. + * + * @privateRemarks + * This is available on Context so that it can be monkey-patched by typedoc-plugin-missing-exports + * It might also turn out to be generally useful for other plugin users. + */ + createSymbolId(symbol: ts.Symbol, declaration?: ts.Declaration) { + return createSymbolIdImpl(symbol, declaration); + } + addChild(reflection: DeclarationReflection | DocumentReflection) { if (this.scope instanceof ContainerReflection) { this.scope.addChild(reflection); @@ -276,7 +308,7 @@ export class Context { } shouldIgnore(symbol: ts.Symbol) { - return this.converter.shouldIgnore(symbol); + return this.converter.shouldIgnore(symbol, this.checker); } /** @@ -289,7 +321,7 @@ export class Context { registerReflection(reflection: Reflection, symbol: ts.Symbol | undefined, filePath?: NormalizedPath) { if (symbol) { this.reflectionIdToSymbolMap.set(reflection.id, symbol); - const id = createSymbolId(symbol); + const id = this.createSymbolId(symbol); // #2466 // If we just registered a member of a class or interface, then we need to check if @@ -326,7 +358,7 @@ export class Context { } getReflectionFromSymbol(symbol: ts.Symbol) { - return this.project.getReflectionFromSymbolId(createSymbolId(symbol)); + return this.project.getReflectionFromSymbolId(this.createSymbolId(symbol)); } getSymbolFromReflection(reflection: Reflection) { @@ -338,14 +370,21 @@ export class Context { this._program = program; } + private createCommentContext(): CommentContext { + return { + config: this.converter.config, + logger: this.logger, + checker: this.checker, + files: this.project.files, + createSymbolId: (s, d) => this.createSymbolId(s, d), + }; + } + getComment(symbol: ts.Symbol, kind: ReflectionKind) { return getComment( symbol, kind, - this.converter.config, - this.logger, - this.checker, - this.project.files, + this.createCommentContext(), ); } @@ -353,20 +392,14 @@ export class Context { return getNodeComment( node, moduleComment, - this.converter.config, - this.logger, - this.checker, - this.project.files, + this.createCommentContext(), ); } getFileComment(node: ts.SourceFile) { return getFileComment( node, - this.converter.config, - this.logger, - this.checker, - this.project.files, + this.createCommentContext(), ); } @@ -380,10 +413,7 @@ export class Context { ) { return getJsDocComment( declaration, - this.converter.config, - this.logger, - this.checker, - this.project.files, + this.createCommentContext(), ); } @@ -392,10 +422,7 @@ export class Context { ) { return getSignatureComment( declaration, - this.converter.config, - this.logger, - this.checker, - this.project.files, + this.createCommentContext(), ); } diff --git a/src/lib/converter/converter.ts b/src/lib/converter/converter.ts index 220dbb5c1..9efdab032 100644 --- a/src/lib/converter/converter.ts +++ b/src/lib/converter/converter.ts @@ -10,7 +10,7 @@ import { DocumentReflection, type ParameterReflection, ProjectReflection, - type Reflection, + Reflection, ReflectionKind, type ReflectionSymbolId, type SignatureReflection, @@ -36,7 +36,7 @@ import { unique, } from "#utils"; import type { DocumentationEntryPoint } from "../utils/entry-point.js"; -import type { CommentParserConfig } from "./comments/index.js"; +import { clearCommentCache, type CommentParserConfig } from "./comments/index.js"; import type { CommentStyle, ValidationOptions } from "../utils/options/declaration.js"; import { parseCommentString } from "./comments/parser.js"; import { lexCommentString } from "./comments/rawLexer.js"; @@ -61,6 +61,7 @@ import { SourcePlugin } from "./plugins/SourcePlugin.js"; import { TypePlugin } from "./plugins/TypePlugin.js"; import { IncludePlugin } from "./plugins/IncludePlugin.js"; import { MergeModuleWithPlugin } from "./plugins/MergeModuleWithPlugin.js"; +import { resolveAliasedSymbol } from "./utils/symbols.js"; export interface ConverterEvents { begin: [Context]; @@ -321,6 +322,10 @@ export class Converter extends AbstractComponent { this.application.options.getValue("name"), this.application.files, ); + if (this.owner.options.packageDir) { + project.files.registerReflectionPath(normalizePath(this.owner.options.packageDir), project); + } + const context = new Context(this, programs, project); this.trigger(Converter.EVENT_BEGIN, context); @@ -330,7 +335,17 @@ export class Converter extends AbstractComponent { this.resolve(context); this.trigger(Converter.EVENT_END, context); - this._config = undefined; + + // Delete caches of options so that test usage which changes options + // doesn't have confusing behavior where tests run in isolation work + // but break when run as a batch. + delete this._config; + delete this.excludeCache; + delete this.externalPatternCache; + + // Also clear the comment cache so if we convert this ts.Program again + // later we will re-parse comments. + clearCommentCache(); return project; } @@ -425,25 +440,32 @@ export class Converter extends AbstractComponent { } } + resolveLinks(reflection: Reflection): void; + /** @deprecated just pass the reflection */ resolveLinks(comment: Comment, owner: Reflection): void; resolveLinks( parts: readonly CommentDisplayPart[], owner: Reflection, ): CommentDisplayPart[]; resolveLinks( - comment: Comment | readonly CommentDisplayPart[], - owner: Reflection, + comment: Reflection | Comment | readonly CommentDisplayPart[], + owner?: Reflection, ): CommentDisplayPart[] | undefined { - if (comment instanceof Comment) { + if (comment instanceof Reflection) { resolveLinks( comment, - owner, + (ref, part, refl, id) => this.resolveExternalLink(ref, part, refl, id), + { preserveLinkText: this.preserveLinkText }, + ); + } else if (comment instanceof Comment) { + resolveLinks( + owner!, (ref, part, refl, id) => this.resolveExternalLink(ref, part, refl, id), { preserveLinkText: this.preserveLinkText }, ); } else { return resolvePartLinks( - owner, + owner!, comment, (ref, part, refl, id) => this.resolveExternalLink(ref, part, refl, id), { preserveLinkText: this.preserveLinkText }, @@ -613,12 +635,13 @@ export class Converter extends AbstractComponent { * information at this point since comment discovery hasn't happened. * @internal */ - shouldIgnore(symbol: ts.Symbol) { + shouldIgnore(symbol: ts.Symbol, checker: ts.TypeChecker) { + symbol = resolveAliasedSymbol(symbol, checker); if (this.isExcluded(symbol)) { return true; } - return this.excludeExternals && this.isExternal(symbol); + return this.excludeExternals && this.isExternal(symbol, checker); } private isExcluded(symbol: ts.Symbol) { @@ -631,11 +654,11 @@ export class Converter extends AbstractComponent { } /** @internal */ - isExternal(symbol: ts.Symbol) { + isExternal(symbol: ts.Symbol, checker: ts.TypeChecker) { this.externalPatternCache ??= new MinimatchSet(this.externalPattern); const cache = this.externalPatternCache; - const declarations = symbol.getDeclarations(); + const declarations = resolveAliasedSymbol(symbol, checker).getDeclarations(); // `undefined` has no declarations, if someone does `export default undefined` // the symbol ends up as having no declarations (the export symbol does, but @@ -762,12 +785,9 @@ export class Converter extends AbstractComponent { private _buildCommentParserConfig() { this._config = { blockTags: new Set(this.application.options.getValue("blockTags")), - inlineTags: new Set( - this.application.options.getValue("inlineTags"), - ), - modifierTags: new Set( - this.application.options.getValue("modifierTags"), - ), + inlineTags: new Set(this.application.options.getValue("inlineTags")), + modifierTags: new Set(this.application.options.getValue("modifierTags")), + preservedTypeAnnotationTags: new Set(this.application.options.getValue("preservedTypeAnnotationTags")), jsDocCompatibility: this.application.options.getValue("jsDocCompatibility"), suppressCommentWarningsInDeclarationFiles: this.application.options.getValue( "suppressCommentWarningsInDeclarationFiles", diff --git a/src/lib/converter/factories/signature.ts b/src/lib/converter/factories/signature.ts index 8bc9727bc..9e5f43587 100644 --- a/src/lib/converter/factories/signature.ts +++ b/src/lib/converter/factories/signature.ts @@ -17,7 +17,6 @@ import type { Context } from "../context.js"; import { ConverterEvents } from "../converter-events.js"; import { convertDefaultValue } from "../convert-expression.js"; import { removeUndefined } from "../utils/reflections.js"; -import { createSymbolId } from "./symbol-id.js"; export function createSignature( context: Context, @@ -51,7 +50,7 @@ export function createSignature( if (symbol && declaration) { context.project.registerSymbolId( sigRef, - createSymbolId(symbol, declaration), + context.createSymbolId(symbol, declaration), ); } @@ -164,7 +163,7 @@ export function createConstructSignatureWithType( } } - const parameterSymbols: Array = signature.thisParameter + const parameterSymbols = signature.thisParameter ? [signature.thisParameter, ...signature.parameters] : [...signature.parameters]; @@ -208,7 +207,7 @@ export function createConstructSignatureWithType( function convertParameters( context: Context, sigRef: SignatureReflection, - parameters: readonly (ts.Symbol & { type?: ts.Type })[], + parameters: readonly ts.Symbol[], parameterNodes: | readonly ts.ParameterDeclaration[] | readonly ts.JSDocParameterTag[] @@ -227,7 +226,7 @@ function convertParameters( ts.isJSDocParameterTag(declaration), ); const paramRefl = new ParameterReflection( - /__\d+/.test(param.name) ? "__namedParameters" : param.name, + /^__\d+$/.test(param.name) ? "__namedParameters" : param.name, ReflectionKind.Parameter, sigRef, ); @@ -257,7 +256,7 @@ function convertParameters( typeNode = declaration.typeExpression?.type; } } else { - type = param.type; + type = context.checker.getTypeOfSymbol(param); } if ( @@ -296,8 +295,9 @@ function convertParameters( paramRefl.setFlag(ReflectionFlag.Optional, isOptional); // If we have no declaration, then this is an implicitly defined parameter in JS land - // because the method body uses `arguments`... which is always a rest argument - let isRest = true; + // because the method body uses `arguments`... which is always a rest argument, + // unless it is a this parameter defined with @this in JSDoc. + let isRest = param.name !== "this"; if (declaration) { isRest = ts.isParameter(declaration) ? !!declaration.dotDotDotToken diff --git a/src/lib/converter/factories/symbol-id.ts b/src/lib/converter/factories/symbol-id.ts index d74964250..75c45cdfe 100644 --- a/src/lib/converter/factories/symbol-id.ts +++ b/src/lib/converter/factories/symbol-id.ts @@ -1,16 +1,14 @@ import { ReflectionSymbolId } from "#models"; -import { findPackageForPath, getCommonDirectory, getQualifiedName, normalizePath, readFile } from "#node-utils"; -import { type NormalizedPath, Validation } from "#utils"; -import { existsSync } from "fs"; -import { join, relative, resolve } from "node:path"; +import { findPackageForPath, getQualifiedName, normalizePath, resolveDeclarationMaps } from "#node-utils"; +import { type NormalizedPath } from "#utils"; +import { relative } from "node:path"; import ts from "typescript"; -const declarationMapCache = new Map(); - let transientCount = 0; const transientIds = new WeakMap(); -export function createSymbolId(symbol: ts.Symbol, declaration?: ts.Declaration) { +// Don't use this directly, use Context.createSymbolId instead. +export function createSymbolIdImpl(symbol: ts.Symbol, declaration?: ts.Declaration) { declaration ??= symbol.declarations?.[0]; const tsSource = declaration?.getSourceFile().fileName ?? ""; const sourceFileName = resolveDeclarationMaps(tsSource); @@ -50,66 +48,3 @@ export function createSymbolId(symbol: ts.Symbol, declaration?: ts.Declaration) return id; } - -function resolveDeclarationMaps(file: string): string { - if (!/\.d\.[cm]?ts$/.test(file)) return file; - if (declarationMapCache.has(file)) return declarationMapCache.get(file)!; - - const mapFile = file + ".map"; - if (!existsSync(mapFile)) return file; - - let sourceMap: unknown; - try { - sourceMap = JSON.parse(readFile(mapFile)) as unknown; - } catch { - return file; - } - - if ( - Validation.validate( - { - file: String, - sourceRoot: Validation.optional(String), - sources: [Array, String], - }, - sourceMap, - ) - ) { - // There's a pretty large assumption in here that we only have - // 1 source file per js file. This is a pretty standard typescript approach, - // but people might do interesting things with transpilation that could break this. - let source = sourceMap.sources[0]; - - // If we have a sourceRoot, trim any leading slash from the source, and join them - // Similar to how it's done at https://github.com/mozilla/source-map/blob/58819f09018d56ef84dc41ba9c93f554e0645169/lib/util.js#L412 - if (sourceMap.sourceRoot !== undefined) { - source = source.replace(/^\//, ""); - source = join(sourceMap.sourceRoot, source); - } - - const result = resolve(mapFile, "..", source); - declarationMapCache.set(file, result); - return result; - } - - return file; -} - -// See also: inferEntryPoints in entry-point.ts -export function addInferredDeclarationMapPaths( - opts: ts.CompilerOptions, - files: readonly string[], -) { - const rootDir = opts.rootDir || getCommonDirectory(files); - const declDir = opts.declarationDir || opts.outDir || rootDir; - - for (const file of files) { - const mapFile = normalizePath( - resolve(declDir, relative(rootDir, file)).replace( - /\.([cm]?[tj]s)x?$/, - ".d.$1", - ), - ); - declarationMapCache.set(mapFile, file); - } -} diff --git a/src/lib/converter/jsdoc.ts b/src/lib/converter/jsdoc.ts index 53836f906..ca9be368e 100644 --- a/src/lib/converter/jsdoc.ts +++ b/src/lib/converter/jsdoc.ts @@ -14,7 +14,6 @@ import { import type { Context } from "./context.js"; import { ConverterEvents } from "./converter-events.js"; import { convertParameterNodes, convertTemplateParameterNodes } from "./factories/signature.js"; -import { createSymbolId } from "./factories/symbol-id.js"; export function convertJsDocAlias( context: Context, @@ -134,7 +133,7 @@ function convertJsDocSignature(context: Context, node: ts.JSDocSignature) { ); context.project.registerSymbolId( signature, - createSymbolId(symbol, node), + context.createSymbolId(symbol, node), ); context.registerReflection(signature, void 0); const signatureCtx = rc.withScope(signature); diff --git a/src/lib/converter/plugins/CategoryPlugin.ts b/src/lib/converter/plugins/CategoryPlugin.ts index c68a5aadd..e0279caeb 100644 --- a/src/lib/converter/plugins/CategoryPlugin.ts +++ b/src/lib/converter/plugins/CategoryPlugin.ts @@ -15,6 +15,7 @@ import type { Context } from "../context.js"; import { ConverterEvents } from "../converter-events.js"; import type { Converter } from "../converter.js"; import { i18n } from "#utils"; +import { isValidSortStrategy } from "../../utils/sort.js"; /** * A handler that sorts and categorizes the found reflections in the resolving phase. @@ -22,7 +23,7 @@ import { i18n } from "#utils"; * The handler sets the ´category´ property of all reflections. */ export class CategoryPlugin extends ConverterComponent { - sortFunction!: ( + defaultSortFunction!: ( reflections: Array, ) => void; @@ -71,7 +72,7 @@ export class CategoryPlugin extends ConverterComponent { * Triggered when the converter begins converting a project. */ private setup() { - this.sortFunction = getSortFunction(this.application.options); + this.defaultSortFunction = getSortFunction(this.application.options); // Set up static properties if (this.defaultCategory) { @@ -203,12 +204,25 @@ export class CategoryPlugin extends ConverterComponent { } for (const cat of categories.values()) { - this.sortFunction(cat.children); + this.getSortFunction(parent)(cat.children); } return Array.from(categories.values()); } + getSortFunction(reflection: ContainerReflection) { + const tag = reflection.comment?.getTag("@sortStrategy"); + if (tag) { + const text = Comment.combineDisplayParts(tag.content); + // We don't need to warn about invalid strategies here because the group plugin + // runs first and will have already warned. + const strategies = text.split(/[,\s]+/).filter(isValidSortStrategy); + return getSortFunction(this.application.options, strategies); + } + + return this.defaultSortFunction; + } + /** * Callback used to sort categories by name. * diff --git a/src/lib/converter/plugins/CommentPlugin.ts b/src/lib/converter/plugins/CommentPlugin.ts index b59d5cd43..56f77f15c 100644 --- a/src/lib/converter/plugins/CommentPlugin.ts +++ b/src/lib/converter/plugins/CommentPlugin.ts @@ -22,6 +22,7 @@ import { removeIf, removeIfPresent, setIntersection, + type TagString, unique, } from "#utils"; import { ConverterComponent } from "../components.js"; @@ -36,9 +37,9 @@ import { CategoryPlugin } from "./CategoryPlugin.js"; * (for JS users) will be consumed by TypeScript and need not be preserved * in the comment. * - * Note that param/arg/argument/return/returns are not present. + * Note that param/arg/argument/return/returns/this are not present. * These tags will have their type information stripped when parsing, but still - * provide useful information for documentation. + * may provide useful information for documentation. */ const NEVER_RENDERED = [ "@augments", @@ -47,7 +48,6 @@ const NEVER_RENDERED = [ "@constructor", "@enum", "@extends", - "@this", "@type", "@typedef", "@jsx", @@ -56,7 +56,7 @@ const NEVER_RENDERED = [ // We might make this user configurable at some point, but for now, // this set is configured here. const MUTUALLY_EXCLUSIVE_MODIFIERS = [ - new Set<`@${string}`>([ + new Set([ "@alpha", "@beta", "@experimental", @@ -122,10 +122,10 @@ const MUTUALLY_EXCLUSIVE_MODIFIERS = [ */ export class CommentPlugin extends ConverterComponent { @Option("excludeTags") - accessor excludeTags!: `@${string}`[]; + accessor excludeTags!: TagString[]; @Option("cascadedModifierTags") - accessor cascadedModifierTags!: `@${string}`[]; + accessor cascadedModifierTags!: TagString[]; @Option("excludeInternal") accessor excludeInternal!: boolean; @@ -133,6 +133,9 @@ export class CommentPlugin extends ConverterComponent { @Option("excludePrivate") accessor excludePrivate!: boolean; + @Option("excludePrivateClassFields") + accessor excludePrivateClassFields!: boolean; + @Option("excludeProtected") accessor excludeProtected!: boolean; @@ -475,26 +478,29 @@ export class CommentPlugin extends ConverterComponent { ) { if (!comment) return; - signature.parameters?.forEach((parameter, index) => { - if (parameter.name === "__namedParameters") { - const commentParams = comment.blockTags.filter( - (tag) => tag.tag === "@param" && !tag.name?.includes("."), - ); - if ( - signature.parameters?.length === commentParams.length && - commentParams[index].name - ) { - parameter.name = commentParams[index].name!; - } + const unusedCommentParams = comment.blockTags.filter( + (tag) => + tag.tag === "@param" && tag.name && !tag.name.includes(".") && + !signature.parameters?.some(p => p.name === tag.name), + ); + + signature.parameters?.forEach((parameter) => { + if (parameter.name === "__namedParameters" && unusedCommentParams.length) { + parameter.name = unusedCommentParams[0].name!; + unusedCommentParams.splice(0, 1); } const tag = comment.getIdentifiedTag(parameter.name, "@param"); if (tag) { - parameter.comment = new Comment( - Comment.cloneDisplayParts(tag.content), - ); + parameter.comment = new Comment(Comment.cloneDisplayParts(tag.content)); parameter.comment.sourcePath = comment.sourcePath; + } else if (parameter.name === "this") { + const thisTag = comment.getTag("@this"); + if (thisTag) { + parameter.comment = new Comment(Comment.cloneDisplayParts(thisTag.content)); + parameter.comment.sourcePath = comment.sourcePath; + } } }); @@ -512,6 +518,7 @@ export class CommentPlugin extends ConverterComponent { this.validateParamTags(signature, comment, signature.parameters || []); + comment.removeTags("@this"); comment.removeTags("@param"); comment.removeTags("@typeParam"); comment.removeTags("@template"); @@ -566,6 +573,19 @@ export class CommentPlugin extends ConverterComponent { return true; } + // #3017 this isn't quite right as it may incorrectly detect + // private members named with a hash which aren't actually private + // class fields (e.g. class Foo { "#hash" = 1 }) + // We can't fix this without storing more information about the name, + // which I'm going to put off until #3015 is done. + if ( + reflection.flags.hasFlag(ReflectionFlag.Private) && + reflection.name.startsWith("#") && + this.excludePrivateClassFields + ) { + return true; + } + if ( reflection.flags.hasFlag(ReflectionFlag.Protected) && this.excludeProtected diff --git a/src/lib/converter/plugins/GroupPlugin.ts b/src/lib/converter/plugins/GroupPlugin.ts index 441c97074..49cc8d087 100644 --- a/src/lib/converter/plugins/GroupPlugin.ts +++ b/src/lib/converter/plugins/GroupPlugin.ts @@ -9,14 +9,14 @@ import { import { ReflectionGroup } from "../../models/ReflectionGroup.js"; import { ConverterComponent } from "../components.js"; import type { Context } from "../context.js"; -import { getSortFunction } from "../../utils/sort.js"; -import { Option } from "../../utils/index.js"; +import { getSortFunction, isValidSortStrategy, SORT_STRATEGIES } from "../../utils/sort.js"; +import { Option, type SortStrategy } from "../../utils/index.js"; import { Comment } from "../../models/index.js"; import { ConverterEvents } from "../converter-events.js"; import type { Converter } from "../converter.js"; import { ApplicationEvents } from "../../application-events.js"; import assert from "assert"; -import { i18n } from "#utils"; +import { i18n, partition } from "#utils"; // Same as the defaultKindSortOrder in sort.ts const defaultGroupOrder = [ @@ -47,7 +47,7 @@ const defaultGroupOrder = [ * The handler sets the `groups` property of all container reflections. */ export class GroupPlugin extends ConverterComponent { - sortFunction!: ( + defaultSortFunction!: ( reflections: Array, ) => void; @@ -107,7 +107,7 @@ export class GroupPlugin extends ConverterComponent { } private setup() { - this.sortFunction = getSortFunction(this.application.options); + this.defaultSortFunction = getSortFunction(this.application.options); GroupPlugin.WEIGHTS = this.groupOrder; if (GroupPlugin.WEIGHTS.length === 0) { GroupPlugin.WEIGHTS = defaultGroupOrder.map((kind) => ReflectionKind.pluralString(kind)); @@ -115,19 +115,21 @@ export class GroupPlugin extends ConverterComponent { } private group(reflection: ContainerReflection) { + const sortFunction = this.getSortFunction(reflection); + if (reflection.childrenIncludingDocuments && !reflection.groups) { if (reflection.children) { if ( this.sortEntryPoints || !reflection.children.some((c) => c.kindOf(ReflectionKind.Module)) ) { - this.sortFunction(reflection.children); - this.sortFunction(reflection.documents || []); - this.sortFunction(reflection.childrenIncludingDocuments); + sortFunction(reflection.children); + sortFunction(reflection.documents || []); + sortFunction(reflection.childrenIncludingDocuments); } } else if (reflection.documents) { - this.sortFunction(reflection.documents); - this.sortFunction(reflection.childrenIncludingDocuments); + sortFunction(reflection.documents); + sortFunction(reflection.childrenIncludingDocuments); } if (reflection.comment?.hasModifier("@disableGroups")) { @@ -256,6 +258,25 @@ export class GroupPlugin extends ConverterComponent { return Array.from(groups.values()).sort(GroupPlugin.sortGroupCallback); } + getSortFunction(reflection: ContainerReflection) { + const tag = reflection.comment?.getTag("@sortStrategy"); + if (tag) { + const text = Comment.combineDisplayParts(tag.content); + const strategies = text.split(/[,\s]+/); + const [valid, invalid] = partition(strategies, isValidSortStrategy); + for (const inv of invalid) { + this.application.logger.warn(i18n.comment_for_0_specifies_1_as_sort_strategy_but_only_2_is_valid( + reflection.getFriendlyFullName(), + inv, + SORT_STRATEGIES.join("\n\t"), + )); + } + return getSortFunction(this.application.options, valid as SortStrategy[]); + } + + return this.defaultSortFunction; + } + /** * Callback used to sort groups by name. */ diff --git a/src/lib/converter/plugins/ImplementsPlugin.ts b/src/lib/converter/plugins/ImplementsPlugin.ts index 8d53dedb5..29bb78146 100644 --- a/src/lib/converter/plugins/ImplementsPlugin.ts +++ b/src/lib/converter/plugins/ImplementsPlugin.ts @@ -9,11 +9,11 @@ import { ReflectionKind, SignatureReflection, } from "../../models/index.js"; -import { ReferenceType, ReflectionType, type Type } from "../../models/types.js"; +import { ReferenceType, ReflectionType, type SomeType, type Type } from "../../models/types.js"; import { filterMap, type TranslatedString, zip } from "#utils"; import { ConverterComponent } from "../components.js"; import type { Context } from "../context.js"; -import { getHumanName } from "../../utils/index.js"; +import { findPackageForPath, getHumanName } from "../../utils/index.js"; import { ConverterEvents } from "../converter-events.js"; import type { Converter } from "../converter.js"; @@ -110,8 +110,10 @@ export class ImplementsPlugin extends ConverterComponent { project: ProjectReflection, reflection: DeclarationReflection, ) { + if (!reflection.extendedTypes) return; + const extendedTypes = filterMap( - reflection.extendedTypes ?? [], + reflection.extendedTypes, (type) => { return type instanceof ReferenceType && type.reflection instanceof DeclarationReflection @@ -139,20 +141,75 @@ export class ImplementsPlugin extends ConverterComponent { parentMember.signatures ?? [], ) ) { - childSig[key] = ReferenceType.createResolvedReference( + // If we're already pointing at something because TS said we should reference + // it, then don't overwrite the reference. + if (!childSig[key]?.reflection) { + childSig[key] = ReferenceType.createResolvedReference( + `${parent.name}.${parentMember.name}`, + parentSig, + project, + ); + } + } + + if (!child[key]?.reflection) { + child[key] = ReferenceType.createResolvedReference( `${parent.name}.${parentMember.name}`, - parentSig, + parentMember, project, ); } - child[key] = ReferenceType.createResolvedReference( - `${parent.name}.${parentMember.name}`, - parentMember, + this.handleInheritedComments(child, parentMember); + } + } + } + + // #2978, this is very unfortunate. If a child's parent links are broken at this point, + // we replace them with an intentionally broken link so that they won't ever be resolved. + // This is done because if we don't do it then we run into issues where we have a link which + // points to some ReflectionSymbolId which might not exist now, but once we've gone through + // serialization/deserialization, might point to an unexpected location. (See the mixin + // converter tests, I suspect this might actually be an indication of something else slightly + // broken there, but don't want to spend more time with this right now.) + // #2982/#3007, even more unfortunately, we only want to keep the link if it is pointing + // to a reflection which will receive a link during rendering, we pick this based on it being + // the type of member we expect to point to. + const isValidRef = (ref: ReferenceType) => + !!ref.reflection?.parent?.kindOf( + ReflectionKind.ClassOrInterface | ReflectionKind.Method | ReflectionKind.Constructor, + ); + + for (const child of reflection.children || []) { + if (child.inheritedFrom && !isValidRef(child.inheritedFrom)) { + child.inheritedFrom = ReferenceType.createBrokenReference( + child.inheritedFrom.name, + project, + child.inheritedFrom.package, + ); + } + if (child.overwrites && !isValidRef(child.overwrites)) { + child.overwrites = ReferenceType.createBrokenReference( + child.overwrites.name, + project, + child.overwrites.package, + ); + } + + for (const childSig of child.getAllSignatures()) { + if (childSig.inheritedFrom && !isValidRef(childSig.inheritedFrom)) { + childSig.inheritedFrom = ReferenceType.createBrokenReference( + childSig.inheritedFrom.name, project, + childSig.inheritedFrom.package, + ); + } + if (childSig.overwrites && !isValidRef(childSig.overwrites)) { + childSig.overwrites = ReferenceType.createBrokenReference( + childSig.overwrites.name, + project, + childSig.overwrites.package, ); - - this.handleInheritedComments(child, parentMember); } } } @@ -234,6 +291,18 @@ export class ImplementsPlugin extends ConverterComponent { }); } + // Remove hidden classes/interfaces which we inherit from + if (reflection.kindOf(ReflectionKind.ClassOrInterface)) { + const notHiddenType = (t: SomeType) => + !(t instanceof ReferenceType) || + !t.symbolId || + !project.symbolIdHasBeenRemoved(t.symbolId); + reflection.implementedTypes = reflection.implementedTypes?.filter(notHiddenType); + if (!reflection.implementedTypes?.length) delete reflection.implementedTypes; + reflection.extendedTypes = reflection.extendedTypes?.filter(notHiddenType); + if (!reflection.extendedTypes?.length) delete reflection.extendedTypes; + } + if ( reflection.kindOf(ReflectionKind.ClassOrInterface) && reflection.extendedTypes @@ -469,6 +538,18 @@ export class ImplementsPlugin extends ConverterComponent { } } +function getConstructorPackagePath(context: Context, clause: ts.ExpressionWithTypeArguments): string | undefined { + const symbol = context.getSymbolAtLocation(clause.expression); + if (!symbol) return undefined; + + const resolvedSymbol = context.resolveAliasedSymbol(symbol); + + const symbolPath = resolvedSymbol?.declarations?.[0]?.getSourceFile().fileName; + if (!symbolPath) return undefined; + + return findPackageForPath(symbolPath)?.[0]; +} + function constructorInheritance( context: Context, reflection: DeclarationReflection, @@ -480,17 +561,21 @@ function constructorInheritance( ); if (!extendsClause) return; - const name = `${extendsClause.types[0].getText()}.constructor`; + const extendsType = extendsClause.types[0]; + const refPackage = getConstructorPackagePath(context, extendsType); + + const name = `${extendsType.getText()}.constructor`; const key = constructorDecl ? "overwrites" : "inheritedFrom"; reflection[key] ??= ReferenceType.createBrokenReference( name, context.project, + refPackage, ); for (const sig of reflection.signatures ?? []) { - sig[key] ??= ReferenceType.createBrokenReference(name, context.project); + sig[key] ??= ReferenceType.createBrokenReference(name, context.project, refPackage); } } @@ -510,9 +595,21 @@ function createLink( symbol: ts.Symbol, isInherit: boolean, ) { - const project = context.project; const name = `${expr.expression.getText()}.${getHumanName(symbol.name)}`; + // We should always have rootSymbols, but check just in case. We use the first + // symbol here as TypeDoc's models don't have multiple symbols for the parent + // reference. This is technically wrong because symbols might be declared in + // multiple locations (interface declaration merging), but that's an uncommon + // enough use case that it doesn't seem worthwhile to complicate the rest of the + // world to deal with it. + // Note that we also need to check that the root symbol isn't this symbol. + // This seems to happen sometimes when dealing with interface inheritance. + const rootSymbols = context.checker.getRootSymbols(symbol); + const ref = rootSymbols.length && rootSymbols[0] != symbol + ? context.createSymbolReference(rootSymbols[0], context, name) + : ReferenceType.createBrokenReference(name, context.project, undefined); + link(reflection); link(reflection.getSignature); link(reflection.setSignature); @@ -523,34 +620,21 @@ function createLink( link(sig); } - // Intentionally create broken links here. These will be replaced with real links during - // resolution if we can do so. We create broken links rather than real links because in the - // case of an inherited symbol, we'll end up referencing a single symbol ID rather than one - // for each class. function link( target: DeclarationReflection | SignatureReflection | undefined, ) { if (!target) return; if (clause.token === ts.SyntaxKind.ImplementsKeyword) { - target.implementationOf ??= ReferenceType.createBrokenReference( - name, - project, - ); + target.implementationOf ??= ref; return; } if (isInherit) { target.setFlag(ReflectionFlag.Inherited); - target.inheritedFrom ??= ReferenceType.createBrokenReference( - name, - project, - ); + target.inheritedFrom ??= ref; } else { - target.overwrites ??= ReferenceType.createBrokenReference( - name, - project, - ); + target.overwrites ??= ref; } } } diff --git a/src/lib/converter/plugins/IncludePlugin.ts b/src/lib/converter/plugins/IncludePlugin.ts index 29a4cfea3..56da69733 100644 --- a/src/lib/converter/plugins/IncludePlugin.ts +++ b/src/lib/converter/plugins/IncludePlugin.ts @@ -358,7 +358,7 @@ type RegionTagRETuple = [ (regionName: string) => RegExp, (regionName: string) => RegExp, ]; -const regionTagREsByExt: Record = { +const regionTagREsByExt: Record = { bat: [ [ (regionName) => new RegExp(`:: *#region *${regionName} *\n`, "g"), @@ -407,14 +407,14 @@ const regionTagREsByExt: Record = { ], }; regionTagREsByExt["fs"] = [ - ...regionTagREsByExt["ts"], + ...regionTagREsByExt["ts"]!, [ (regionName) => new RegExp(`(#_region) *${regionName} *\n`, "g"), (regionName) => new RegExp(`(#_endregion) *${regionName} *\n`, "g"), ], ]; regionTagREsByExt["java"] = [ - ...regionTagREsByExt["ts"], + ...regionTagREsByExt["ts"]!, [ (regionName) => new RegExp(`// * *${regionName} *\n`, "g"), (regionName) => new RegExp(`// * *${regionName} *\n`, "g"), @@ -428,3 +428,5 @@ regionTagREsByExt["php"] = regionTagREsByExt["cs"]; regionTagREsByExt["ps1"] = regionTagREsByExt["cs"]; regionTagREsByExt["py"] = regionTagREsByExt["cs"]; regionTagREsByExt["js"] = regionTagREsByExt["ts"]; +regionTagREsByExt["mts"] = regionTagREsByExt["ts"]; +regionTagREsByExt["cts"] = regionTagREsByExt["ts"]; diff --git a/src/lib/converter/plugins/InheritDocPlugin.ts b/src/lib/converter/plugins/InheritDocPlugin.ts index 56b53409c..b9763e65f 100644 --- a/src/lib/converter/plugins/InheritDocPlugin.ts +++ b/src/lib/converter/plugins/InheritDocPlugin.ts @@ -150,6 +150,8 @@ export class InheritDocPlugin extends ConverterComponent { } target.comment.removeTags("@inheritDoc"); + target.comment.removeTags("@remarks"); + target.comment.removeTags("@returns"); target.comment.summary = Comment.cloneDisplayParts( source.comment.summary, ); diff --git a/src/lib/converter/plugins/LinkResolverPlugin.ts b/src/lib/converter/plugins/LinkResolverPlugin.ts index fb2c876ce..d2086c8f8 100644 --- a/src/lib/converter/plugins/LinkResolverPlugin.ts +++ b/src/lib/converter/plugins/LinkResolverPlugin.ts @@ -2,13 +2,7 @@ import { ConverterComponent } from "../components.js"; import type { Context, Converter } from "../../converter/index.js"; import { ConverterEvents } from "../converter-events.js"; import { Option, type ValidationOptions } from "../../utils/index.js"; -import { - ContainerReflection, - makeRecursiveVisitor, - type ProjectReflection, - type Reflection, - type ReflectionCategory, -} from "../../models/index.js"; +import type { ProjectReflection } from "../../models/index.js"; import { discoverAllReferenceTypes } from "../../utils/reflections.js"; import { ApplicationEvents } from "../../application-events.js"; @@ -40,84 +34,7 @@ export class LinkResolverPlugin extends ConverterComponent { resolveLinks(project: ProjectReflection) { for (const id in project.reflections) { const reflection = project.reflections[id]; - if (reflection.comment) { - this.owner.resolveLinks(reflection.comment, reflection); - } - - if (reflection.isDeclaration()) { - reflection.type?.visit( - makeRecursiveVisitor({ - union: (type) => { - type.elementSummaries = type.elementSummaries?.map( - (parts) => this.owner.resolveLinks(parts, reflection), - ); - }, - }), - ); - - if (reflection.readme) { - reflection.readme = this.owner.resolveLinks( - reflection.readme, - reflection, - ); - } - } - - if (reflection.isDocument()) { - reflection.content = this.owner.resolveLinks( - reflection.content, - reflection, - ); - } - - if ( - reflection.isParameter() && - reflection.type?.type === "reference" && - reflection.type.highlightedProperties - ) { - const resolved = new Map( - Array.from( - reflection.type.highlightedProperties, - ([name, parts]) => { - return [ - name, - this.owner.resolveLinks(parts, reflection), - ]; - }, - ), - ); - - reflection.type.highlightedProperties = resolved; - } - - if (reflection instanceof ContainerReflection) { - if (reflection.groups) { - for (const group of reflection.groups) { - if (group.description) { - group.description = this.owner.resolveLinks( - group.description, - reflection, - ); - } - - if (group.categories) { - for (const cat of group.categories) { - this.resolveCategoryLinks(cat, reflection); - } - } - } - } - - if (reflection.categories) { - for (const cat of reflection.categories) { - this.resolveCategoryLinks(cat, reflection); - } - } - } - } - - if (project.readme) { - project.readme = this.owner.resolveLinks(project.readme, project); + this.owner.resolveLinks(reflection); } for ( @@ -144,16 +61,4 @@ export class LinkResolverPlugin extends ConverterComponent { } } } - - private resolveCategoryLinks( - category: ReflectionCategory, - owner: Reflection, - ) { - if (category.description) { - category.description = this.owner.resolveLinks( - category.description, - owner, - ); - } - } } diff --git a/src/lib/converter/plugins/PackagePlugin.ts b/src/lib/converter/plugins/PackagePlugin.ts index d8f4414d6..5225e910b 100644 --- a/src/lib/converter/plugins/PackagePlugin.ts +++ b/src/lib/converter/plugins/PackagePlugin.ts @@ -6,11 +6,11 @@ import type { ProjectReflection } from "../../models/index.js"; import { ApplicationEvents } from "../../application-events.js"; import { ConverterEvents } from "../converter-events.js"; import type { Converter } from "../converter.js"; -import { type GlobString, i18n, MinimalSourceFile, type NormalizedPath } from "#utils"; +import { type GlobString, i18n, MinimalSourceFile, type NormalizedPath, NormalizedPathUtils } from "#utils"; import { + deriveRootDir, discoverPackageJson, type EntryPointStrategy, - getCommonDirectory, nicePath, normalizePath, Option, @@ -79,7 +79,7 @@ export class PackagePlugin extends ConverterComponent { this.packageJson = undefined; const dirName = this.application.options.packageDir ?? - Path.resolve(getCommonDirectory(this.entryPoints.map(g => `${g}/`))); + Path.resolve(deriveRootDir(this.entryPoints)); this.application.logger.verbose( `Begin package.json search at ${nicePath(dirName)}`, @@ -139,6 +139,10 @@ export class PackagePlugin extends ConverterComponent { ); project.readme = content; + project.files.registerReflectionPath(this.readmeFile, project); + // In packages mode, this probably won't do anything unless someone uses the readme + // option to select a different file. + project.files.registerReflectionPath(NormalizedPathUtils.dirname(this.readmeFile), project); // This isn't ideal, but seems better than figuring out the readme // path over in the include plugin... diff --git a/src/lib/converter/plugins/SourcePlugin.ts b/src/lib/converter/plugins/SourcePlugin.ts index 203e30e84..95d5b2e44 100644 --- a/src/lib/converter/plugins/SourcePlugin.ts +++ b/src/lib/converter/plugins/SourcePlugin.ts @@ -10,7 +10,7 @@ import { SourceReference } from "../../models/index.js"; import { gitIsInstalled, RepositoryManager } from "../utils/repository.js"; import { ConverterEvents } from "../converter-events.js"; import type { Converter } from "../converter.js"; -import { i18n, type NormalizedPath } from "#utils"; +import { i18n } from "#utils"; /** * A handler that attaches source file information to reflections. @@ -31,8 +31,9 @@ export class SourcePlugin extends ConverterComponent { @Option("sourceLinkTemplate") accessor sourceLinkTemplate!: string; - @Option("basePath") - accessor basePath!: NormalizedPath; + get displayBasePath() { + return this.application.options.getValue("displayBasePath") || this.application.options.getValue("basePath"); + } /** * All file names to find the base path from. @@ -157,7 +158,7 @@ export class SourcePlugin extends ConverterComponent { ); } - const basePath = this.basePath || getCommonDirectory([...this.fileNames]); + const basePath = this.displayBasePath || getCommonDirectory([...this.fileNames]); this.repositories ||= new RepositoryManager( basePath, this.gitRevision, diff --git a/src/lib/converter/symbols.ts b/src/lib/converter/symbols.ts index cbcff10c9..8835d6535 100644 --- a/src/lib/converter/symbols.ts +++ b/src/lib/converter/symbols.ts @@ -23,6 +23,7 @@ import { import { convertJsDocAlias, convertJsDocCallback } from "./jsdoc.js"; import { getHeritageTypes } from "./utils/nodes.js"; import { removeUndefined } from "./utils/reflections.js"; +import { resolveAliasedSymbol } from "./utils/symbols.js"; const symbolConverters: { [K in ts.SymbolFlags]?: ( @@ -105,7 +106,7 @@ assert( ); function _convertSymbolNow(context: Context, symbol: ts.Symbol, exportSymbol: ts.Symbol | undefined) { - if (context.shouldIgnore(symbol)) { + if (context.shouldIgnore(resolveAliasedSymbol(symbol, context.checker))) { return; } @@ -408,6 +409,42 @@ function convertTypeAlias( } } +function convertTypeAliasFromValueDeclaration( + context: Context, + symbol: ts.Symbol, + exportSymbol: ts.Symbol | undefined, + valueKind: ReflectionKind, +): undefined { + const comment = context.getComment(symbol, valueKind); + + const reflection = new DeclarationReflection( + exportSymbol?.name || symbol.name, + ReflectionKind.TypeAlias, + context.scope, + ); + reflection.comment = comment; + context.postReflectionCreation(reflection, symbol, exportSymbol); + context.finalizeDeclarationReflection(reflection); + + reflection.type = context.converter.convertType( + context.withScope(reflection), + context.checker.getTypeOfSymbol(symbol), + ); + + if (reflection.type.type === "reflection" && reflection.type.declaration.children) { + // #2817 lift properties of object literal types up to the reflection level. + const typeDecl = reflection.type.declaration; + reflection.project.mergeReflections(typeDecl, reflection); + delete reflection.type; + + // When created any signatures will be created with __type as their + // name, rename them so that they have the alias's name as their name + for (const sig of reflection.signatures || []) { + sig.name = reflection.name; + } + } +} + function attachUnionComments( context: Context, declaration: ts.TypeAliasDeclaration, @@ -494,6 +531,10 @@ function convertFunctionOrMethod( symbol: ts.Symbol, exportSymbol?: ts.Symbol, ): undefined | ts.SymbolFlags { + if (isTypeOnlyExport(exportSymbol)) { + return convertTypeAliasFromValueDeclaration(context, symbol, exportSymbol, ReflectionKind.Function); + } + // Can't just check method flag because this might be called for properties as well // This will *NOT* be called for variables that look like functions, they need a special case. const isMethod = !!( @@ -572,7 +613,7 @@ function convertClassOrInterface( exportSymbol?: ts.Symbol, ) { const reflection = context.createDeclarationReflection( - ts.SymbolFlags.Class & symbol.flags + (ts.SymbolFlags.Class & symbol.flags) && !isTypeOnlyExport(exportSymbol) ? ReflectionKind.Class : ReflectionKind.Interface, symbol, @@ -617,7 +658,7 @@ function convertClassOrInterface( context.finalizeDeclarationReflection(reflection); - if (classDeclaration) { + if (classDeclaration && reflection.kind === ReflectionKind.Class) { // Classes can have static props const staticType = context.checker.getTypeOfSymbolAtLocation( symbol, @@ -983,6 +1024,10 @@ function convertVariable( symbol: ts.Symbol, exportSymbol?: ts.Symbol, ): undefined | ts.SymbolFlags { + if (isTypeOnlyExport(exportSymbol)) { + return convertTypeAliasFromValueDeclaration(context, symbol, exportSymbol, ReflectionKind.Variable); + } + const declaration = symbol.getDeclarations()?.[0]; const comment = context.getComment(symbol, ReflectionKind.Variable); @@ -1088,7 +1133,17 @@ function convertVariableAsEnum( context.finalizeDeclarationReflection(reflection); const rc = context.withScope(reflection); - const declaration = symbol.declarations!.find(ts.isVariableDeclaration)!; + const declaration = symbol.valueDeclaration; + if (!declaration) { + context.logger.error( + i18n.converting_0_as_enum_requires_value_declaration( + symbol.name, + ), + symbol.declarations?.[0], + ); + return; + } + const type = context.checker.getTypeAtLocation(declaration); for (const prop of type.getProperties()) { @@ -1323,6 +1378,20 @@ function convertAccessor( const declaration = symbol.getDeclarations()?.[0]; if (declaration) { setModifiers(symbol, declaration, reflection); + + // #3019, auto accessors `accessor x: string` get the symbol flag for + // an accessor, but they don't have get/set accessors, so the need a type + // set on the accessor reflection structure. + if ( + ts.isPropertyDeclaration(declaration) && + declaration.modifiers?.some(n => n.kind === ts.SyntaxKind.AccessorKeyword) + ) { + reflection.type = context.converter.convertType( + context.withScope(reflection), + context.checker.getTypeOfSymbol(symbol), + declaration.type, + ); + } } context.finalizeDeclarationReflection(reflection); @@ -1484,3 +1553,13 @@ function isFunctionLikeInitializer(node: ts.Expression): boolean { return false; } + +function isTypeOnlyExport(symbol: ts.Symbol | undefined): boolean { + if (!symbol) return false; + + const declaration = symbol.declarations?.[0]; + if (!declaration) return false; + if (!ts.isExportSpecifier(declaration)) return false; + + return declaration.isTypeOnly || declaration.parent.parent.isTypeOnly; +} diff --git a/src/lib/converter/types.ts b/src/lib/converter/types.ts index 27a060e53..34314f130 100644 --- a/src/lib/converter/types.ts +++ b/src/lib/converter/types.ts @@ -35,7 +35,6 @@ import { convertParameterNodes, convertTypeParameterNodes, createSignature } fro import { convertSymbol } from "./symbols.js"; import { isObjectType, isTypeReference } from "./utils/nodes.js"; import { removeUndefined } from "./utils/reflections.js"; -import { createSymbolId } from "./factories/symbol-id.js"; export interface TypeConverter< TNode extends ts.TypeNode = ts.TypeNode, @@ -93,6 +92,7 @@ export function loadConverters() { // Only used if skipLibCheck: true jsDocNullableTypeConverter, jsDocNonNullableTypeConverter, + jsDocAllTypeConverter, ] ) { for (const key of actor.kind) { @@ -276,7 +276,7 @@ const constructorConverter: TypeConverter = { } context.project.registerSymbolId( signature, - createSymbolId(symbol, node), + context.createSymbolId(symbol, node), ); context.registerReflection(signature, void 0); const signatureCtx = rc.withScope(signature); @@ -379,7 +379,7 @@ const functionTypeConverter: TypeConverter = { ); context.project.registerSymbolId( signature, - createSymbolId(symbol, node), + context.createSymbolId(symbol, node), ); context.registerReflection(signature, undefined); const signatureCtx = rc.withScope(signature); @@ -705,6 +705,7 @@ const queryConverter: TypeConverter = { ReferenceType.createBrokenReference( node.exprName.getText(), context.project, + undefined, ), ); } @@ -786,11 +787,23 @@ const referenceConverter: TypeConverter< const ref = ReferenceType.createBrokenReference( context.checker.typeToString(type), context.project, + undefined, ); ref.refersToTypeParameter = true; return ref; } + // #2954 mapped type aliases are special! The type that we have here will + // not point at the type alias which names it like we want, but instead at + // the mapped type instantiation. Fall back to converting via the original + // type node to avoid creating a reference which points to the mapped type. + if ( + originalNode && ts.isTypeReferenceNode(originalNode) && isObjectType(type) && + type.objectFlags & ts.ObjectFlags.Mapped + ) { + return referenceConverter.convert(context, originalNode); + } + let name: string; if (ts.isIdentifier(node.typeName)) { name = node.typeName.text; @@ -1009,7 +1022,7 @@ const thisConverter: TypeConverter = { }, }; -const tupleConverter: TypeConverter = { +const tupleConverter = { kind: [ts.SyntaxKind.TupleType], convert(context, node) { const elements = node.elements.map((node) => convertType(context, node)); @@ -1061,7 +1074,7 @@ const tupleConverter: TypeConverter = { return new TupleType(elements ?? []); }, -}; +} satisfies TypeConverter; const supportedOperatorNames = { [ts.SyntaxKind.KeyOfKeyword]: "keyof", @@ -1119,7 +1132,7 @@ const unionConverter: TypeConverter = { convertType(context, type) { const types = type.types.map((type) => convertType(context, type)); normalizeUnion(types); - sortLiteralUnion(types); + sortUnion(types); return new UnionType(types); }, @@ -1154,6 +1167,15 @@ const jsDocNonNullableTypeConverter: TypeConverter = { convertType: requestBugReport, }; +const jsDocAllTypeConverter: TypeConverter = { + kind: [ts.SyntaxKind.JSDocAllType], + convert() { + return new IntrinsicType("any"); + }, + // Should be a UnionType + convertType: requestBugReport, +}; + function requestBugReport(context: Context, nodeOrType: ts.Node | ts.Type) { if ("kind" in nodeOrType) { const kindName = ts.SyntaxKind[nodeOrType.kind]; @@ -1198,18 +1220,30 @@ function kindToModifier( } } -function sortLiteralUnion(types: SomeType[]) { - if ( - types.some((t) => t.type !== "literal" || typeof t.value !== "number") - ) { +function sortUnion(types: SomeType[]) { + // If every member of the union is a literal numeric type, sort in ascending order + if (types.every(t => t.type === "literal" && typeof t.value === "number")) { + types.sort((a, b) => { + const aLit = a as LiteralType; + const bLit = b as LiteralType; + + return (aLit.value as number) - (bLit.value as number); + }); return; } + // #3024 Otherwise, leave the union in the converted order with the exception of null + // and undefined, which should be sorted last, with null before undefined. types.sort((a, b) => { - const aLit = a as LiteralType; - const bLit = b as LiteralType; + const aIsNull = a.type === "literal" && a.value === null; + const aIsUndef = a.type === "intrinsic" && a.name === "undefined"; + const bIsNull = b.type === "literal" && b.value === null; + const bIsUndef = b.type === "intrinsic" && b.name === "undefined"; + + const aWeight = aIsNull ? 1 : aIsUndef ? 2 : 0; + const bWeight = bIsNull ? 1 : bIsUndef ? 2 : 0; - return (aLit.value as number) - (bLit.value as number); + return aWeight - bWeight; }); } @@ -1265,6 +1299,12 @@ function convertTypeInlined(context: Context, type: ts.Type): SomeType { const elementType = convertType(context, context.checker.getTypeArguments(type as ts.TypeReference)[0]); return new ArrayType(elementType); } + if (isTypeReference(type) && context.checker.isTupleType(type)) { + const tupleNode = context.checker.typeToTypeNode(type.target, void 0, ts.NodeBuilderFlags.IgnoreErrors)!; + if (ts.isTupleTypeNode(tupleNode)) { + return tupleConverter.convertType(context, type as ts.TupleTypeReference, tupleNode); + } + } return typeLiteralConverter.convertType( context, diff --git a/src/lib/converter/utils/repository.ts b/src/lib/converter/utils/repository.ts index 831a54c13..66cd170ca 100644 --- a/src/lib/converter/utils/repository.ts +++ b/src/lib/converter/utils/repository.ts @@ -126,7 +126,7 @@ export class GitRepository implements Repository { logger: Logger, ): GitRepository | undefined { gitRevision ||= git("-C", path, "rev-parse", "HEAD").stdout.trim(); - if (!gitRevision) return; // Will only happen in a repo with no commits. + if (gitRevision == "HEAD") return; // Will only happen in a repo with no commits. let urlTemplate: string | undefined; if (sourceLinkTemplate) { diff --git a/src/lib/internationalization/internationalization.ts b/src/lib/internationalization/internationalization.ts index c802ece18..820de94f4 100644 --- a/src/lib/internationalization/internationalization.ts +++ b/src/lib/internationalization/internationalization.ts @@ -66,7 +66,7 @@ export function loadTranslations(lang: string): Record { try { return req(`./locales/${lang}.cjs`); } catch { - return {}; + return loadTranslations("en"); } } diff --git a/src/lib/internationalization/locales/de.cts b/src/lib/internationalization/locales/de.cts new file mode 100644 index 000000000..1138d57b9 --- /dev/null +++ b/src/lib/internationalization/locales/de.cts @@ -0,0 +1,571 @@ +// Please DO NOT include machine generated translations here. +// If adding a new key, leave it commented out for a native speaker +// to update. + +import localeUtils = require("../locale-utils.cjs"); + +export = localeUtils.buildIncompleteTranslation({ + loaded_multiple_times_0: + "TypeDoc wurde mehrfach geladen. Das wird oft von Plugins verursacht, die auch TypeDoc installiert haben. Die Pfade, von denen TypeDoc geladen wurde, sind:\n\t{0}", + unsupported_ts_version_0: + "Sie verwenden eine Version von TypeScript, die nicht unterstützt wird! Stürzt TypeDoc ab, ist das der Grund. TypeDoc unterstützt {0}", + no_compiler_options_set: + "Keine Compiler-Optionen gesetzt. Das bedeutet wahrscheinlich, dass TypeDoc die tsconfig.json nicht finden konnte. Die generierte Dokumentation wird wahrscheinlich leer sein", + + loaded_plugin_0: "Plugin {0} geladen", + + solution_not_supported_in_watch_mode: + "Die angegebene tsconfig-Datei sieht nach einer Solution-Style-tsconfig aus, die nicht im Watch-Modus unterstützt wird", + strategy_not_supported_in_watch_mode: + "entryPointStrategy muss für den Watch-Modus entweder auf resolve oder expand gesetzt werden", + file_0_changed_restarting: "Konfigurationsdatei {0} wurde verändert: Kompletter Neustart erforderlich...", + file_0_changed_rebuilding: "Datei {0} wurde verändert: Baue Ausgabe neu...", + found_0_errors_and_1_warnings: "{0} Fehler und {1} Warnungen gefunden", + + output_0_could_not_be_generated: "{0}-Ausgabe konnte aufgrund obiger Fehler nicht erstellt werden", + output_0_generated_at_1: "{0} wurde generiert in {1}", + + no_entry_points_for_packages: + "Keine Einstiegspunkte für den packages-Modus angegeben, Dokumentation kann nicht generiert werden", + failed_to_find_packages: + "Konnte keine Packages finden, stellen Sie sicher, dass mindestens ein Verzeichnis mit einer package.json als Einstiegspunkt angegeben wurde", + nested_packages_unsupported_0: + "Projekt unter {0} hat die entryPointStrategy auf packages gesetzt, aber geschachtelte Packages werden nicht unterstützt", + package_option_0_should_be_specified_at_root: + "Die Option packageOptions setzt die Option {0}, welche nur auf Root-Ebene eine Auswirkung hat", + previous_error_occurred_when_reading_options_for_0: + "Der vorangegangene Fehler trat auf, als die Optionen für das Package unter {0} gelesen wurden", + converting_project_at_0: "Konvertiere Projekt unter {0}", + failed_to_convert_packages: + "Konnte ein oder mehrere Packages nicht konvertieren, Ergebnisse werden nicht zusammengeführt", + merging_converted_projects: "Führe konvertierte Projekte zusammen", + + no_entry_points_to_merge: "Keine Einstiegspunkte zum Zusammenführen angegeben", + entrypoint_did_not_match_files_0: "Der Glob {0} für den Einstiegspunkt passte auf keine Dateien", + failed_to_parse_json_0: "Konnte Datei unter {0} nicht als JSON parsen", + + failed_to_read_0_when_processing_document_tag_in_1: + "Fehler beim Einlesen der Datei {0} während der Verarbeitung des @document-Tags vom Kommentar in {1}", + failed_to_read_0_when_processing_project_document: + "Fehler beim Einlesen der Datei {0} während des Hinzufügens des Projekt-Dokuments", + failed_to_read_0_when_processing_document_child_in_1: + "Fehler beim Einlesen der Datei {0} während der Verarbeitung der Dokument-Kindelemente in {1}", + frontmatter_children_0_should_be_an_array_of_strings_or_object_with_string_values: + "Kinder der Frontmatter in {0} sollten entweder ein Array von Strings oder ein Objekt mit String-Werten sein", + converting_union_as_interface: + "Nutzung von @interface auf einem Union-Typ verwirft alle Eigenschaften, die nicht in allen Teilen der Union vorhanden sind. TypeDocs Ausgabe spiegelt möglicherweise den Quellcode nicht korrekt wider.", + converting_0_as_class_requires_value_declaration: + "Konvertierung von {0} als Klasse erfordert eine Klassen-Deklaration, die einen Wert und nicht nur einen Typ darstellt", + converting_0_as_class_without_construct_signatures: + "{0} wird als Klasse konvertiert, hat aber keine Konstruktor-Signaturen", + + comment_for_0_should_not_contain_block_or_modifier_tags: + "Das Kommentar für {0} sollte keine Block- oder Modifier-Tags enthalten", + + symbol_0_has_multiple_declarations_with_comment: + "{0} hat mehrere Deklarationen mit Kommentaren. Ein beliebiges Kommentar wird verwendet werden", + comments_for_0_are_declared_at_1: "Die Kommentare für {0} sind deklariert in:\n\t{1}", + + // comments/parser.ts + multiple_type_parameters_on_template_tag_unsupported: + "TypeDoc unterstützt mehrfache Typenparameter nicht, wenn diese in einem einzelnen @template-Tag mit Kommentar definiert sind", + failed_to_find_jsdoc_tag_for_name_0: + "Konnte JSDoc-Tag für {0} nach dem Parsen der Kommentare nicht finden, bitte erstellen Sie einen Bug-Report", + relative_path_0_is_not_a_file_and_will_not_be_copied_to_output: + "Der relative Pfad {0} ist keine Datei und wird daher nicht mit in das Ausgabeverzeichnis kopiert", + + inline_inheritdoc_should_not_appear_in_block_tag_in_comment_at_0: + "Inline-@inheritDoc-Tag sollte nicht innerhalb eines Block-Tags verwendet werden. Solche Tags im Kommentar unter {0} können nicht verarbeitet werden", + at_most_one_remarks_tag_expected_in_comment_at_0: + "Höchstens ein @remarks-Tag darf in einem Kommentar verwendet werden. Alle außer dem ersten Tag im Kommentar unter {0} werden ignoriert", + at_most_one_returns_tag_expected_in_comment_at_0: + "Höchstens ein @returns-Tag darf in einem Kommentar verwendet werden. Alle außer dem ersten Tag im Kommentar unter {0} werden ignoriert", + at_most_one_inheritdoc_tag_expected_in_comment_at_0: + "Höchstens ein @inheritDoc-Tag darf in einem Kommentar verwendet werden. Alle außer dem ersten Tag im Kommentar unter {0} werden ignoriert", + content_in_summary_overwritten_by_inheritdoc_in_comment_at_0: + "Inhalt in der Zusammenfassung des Kommentars unter {0} wird vom @inheritDoc-Tag überschrieben werden", + content_in_remarks_block_overwritten_by_inheritdoc_in_comment_at_0: + "Inhalt im @remarks-Block des Kommentars unter {0} wird vom @inheritDoc-Tag überschrieben werden", + example_tag_literal_name: + "Die erste Zeile eines @example-Tags wird wortwörtlich als Name des Beispiels interpretiert und sollte nur Text enthalten", + inheritdoc_tag_properly_capitalized: "Der @inheritDoc-Tag sollte korrekte Groß- und Kleinschreibung verwenden", + treating_unrecognized_tag_0_as_modifier: "Behandle unerkannten Tag {0} als Modifier-Tag", + unmatched_closing_brace: "Nicht übereinstimmende schließende Klammern", + unescaped_open_brace_without_inline_tag: "Unmaskierte öffnende Klammer ohne Inline-Tag vorgefunden", + unknown_block_tag_0: "Unbekannter Block-Tag {0} vorgefunden", + unknown_inline_tag_0: "Unbekannter Inline-Tag {0} vorgefunden", + open_brace_within_inline_tag: + "Öffnende Klammer innerhalb eines Inline-Tags vorgefunden, das ist wahrscheinlich ein Fehler", + inline_tag_not_closed: "Inline-Tag wurde nicht geschlossen", + + // validation + failed_to_resolve_link_to_0_in_comment_for_1: `Konnte Link zu "{0}" im Kommentar für {1} nicht auflösen`, + failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2: + `Konnte Link zu "{0}" im Kommentar für {1} nicht auflösen. Meinten Sie vielleicht "{2}"`, + failed_to_resolve_link_to_0_in_readme_for_1: `Konnte Link zu "{0}" in Readme für {1} nicht auflösen`, + failed_to_resolve_link_to_0_in_readme_for_1_may_have_meant_2: + `Konnte Link zu "{0}" in Readme für {1} nicht auflösen. Meinten Sie vielleicht "{2}"`, + failed_to_resolve_link_to_0_in_document_1: `Konnte Link zu "{0}" im Dokument {1} nicht auflösen`, + failed_to_resolve_link_to_0_in_document_1_may_have_meant_2: + `Konnte Link zu "{0}" im Dokument {1} nicht auflösen. Meinten Sie vielleicht "{2}"`, + type_0_defined_in_1_is_referenced_by_2_but_not_included_in_docs: + "{0}, definiert in {1}, wird referenziert von {2}, ist aber nicht in der Dokumentation enthalten", + reflection_0_kind_1_defined_in_2_does_not_have_any_documentation: + "{0} ({1}), definiert in {2}, hat keinerlei Dokumentation", + invalid_intentionally_not_documented_names_0: + "Die folgenden qualifizierten Reflection-Namen wurden absichtlich als undokumentiert markiert, wurden aber entweder in der Dokumentation nicht referenziert oder werden dokumentiert:\n\t{0}", + invalid_intentionally_not_exported_symbols_0: + "Die folgenden Symbole wurden absichtlich als nicht exportiert markiert, wurden aber entweder in der Dokumentation nicht referenziert oder werden dokumentiert:\n\t{0}", + reflection_0_has_unused_mergeModuleWith_tag: + "{0} hat einen @mergeModuleWith-Tag, der nicht aufgelöst werden konnte", + reflection_0_links_to_1_with_text_2_but_resolved_to_3: + `"{0}" verlinkt auf "{1}" mit Text "{2}", welcher zwar existiert, aber keinen Link in der Dokumentation hat. Verlinke stattdessen auf "{3}"`, + + // conversion plugins + not_all_search_category_boosts_used_0: + "Nicht alle in searchCategoryBoosts angegebenen Kategorien werden in der Dokumentation verwendet. Die unbenutzten Kategorien sind:\n\t{0}", + not_all_search_group_boosts_used_0: + "Nicht alle in searchGroupBoosts angegebenen Gruppen werden in der Dokumentation verwendet. Die unbenutzten Gruppen sind:\n\t{0}", + comment_for_0_includes_categoryDescription_for_1_but_no_child_in_group: + `Kommentar für {0} enthält @categoryDescription für "{1}", aber kein Kind wurde in dieser Kategorie platziert`, + comment_for_0_includes_groupDescription_for_1_but_no_child_in_group: + `Kommentar für {0} enthält @groupDescription für "{1}", aber kein Kind wurde in dieser Gruppe platziert`, + label_0_for_1_cannot_be_referenced: + `Das Label "{0}" für {1} kann nicht mit einer Deklarationsreferenz referenziert werden. Labels dürfen nur A-Z, 0-9 sowie _ enthalten und dürfen nicht mit einer Ziffer beginnen`, + modifier_tag_0_is_mutually_exclusive_with_1_in_comment_for_2: + "Der Modifier-Tag {0} darf nicht gleichzeitig mit {1} verwendet werden im Kommentar für {2}", + signature_0_has_unused_param_with_name_1: + `Die Signatur {0} enthält einen @param mit Namen "{1}", der nicht verwendet wird`, + declaration_reference_in_inheritdoc_for_0_not_fully_parsed: + "Deklarationsreferenz in @inheritDoc für {0} wurde nicht vollständig geparst und wird möglicherweise falsch aufgelöst werden", + failed_to_find_0_to_inherit_comment_from_in_1: + `Konnte "{0}" zum Erben des Kommentars nicht finden. Betrifft Kommentar für {1}`, + reflection_0_tried_to_copy_comment_from_1_but_source_had_no_comment: + "{0} hat versucht, ein Kommentar von {1} mit @inheritDoc zu kopieren, aber die Quelle hat kein zugehöriges Kommentar", + inheritdoc_circular_inheritance_chain_0: "@inheritDoc spezifiziert eine zyklische Vererbungskette: {0}", + provided_readme_at_0_could_not_be_read: "Angegebener README-Pfad {0} konnte nicht gelesen werden", + defaulting_project_name: + 'Die Option --name wurde nicht angegeben und kein package.json wurde gefunden. Verwende "Dokumentation" als Rückfallwert für den Projektnamen', + disable_git_set_but_not_source_link_template: + "disableGit wurde gesetzt, aber sourceLinkTemplate nicht, sodass Links auf die Quellcode-Dateien nicht erstellt werden können. Setzen Sie sourceLinkTemplate oder disableSources, um das Ermitteln der Quellcode-Dateien zu deaktivieren", + disable_git_set_and_git_revision_used: + "disableGit wurde gesetzt und sourceLinkTemplate enthält {gitRevision}, was mit dem Leerstring ersetzt wird, da keine Revision angegeben wurde", + git_remote_0_not_valid: + `Das angegebene Git-Remote "{0}" war nicht gültig. Links auf Quellcode-Dateien werden nicht funktionieren`, + reflection_0_tried_to_merge_into_child_1: + "Die Reflection {0} versuchte mittels @mergeModuleWith, sich in eines ihrer Kinder einzufügen: {1}", + + include_0_in_1_specified_2_resolved_to_3_does_not_exist: + `{0}-Tag im Kommentar für {1} gab "{2}" zum Einbinden an, was zu "{3}" aufgelöst wurde und nicht existiert oder keine Datei ist.`, + include_0_in_1_specified_2_circular_include_3: + `{0}-Tag im Kommentar für {1} gab "{2}" zum Einbinden an, was in einer zyklischen Einbindung resultierte:\n\t{3}`, + include_0_tag_in_1_specified_2_file_3_region_4_region_not_found: + `{0}-Tag in {1} gab "{2}" zum Einbinden der Region mit Label "{4}" aus Datei "{3}" an, aber die Region wurde nicht in der Datei gefunden.`, + include_0_tag_in_1_region_2_region_not_supported: + `{0}-Tag in {1} gab "{2}" an, aber Regionen werden für die Dateierweiterung nicht unterstützt.`, + include_0_tag_in_1_specified_2_file_3_region_4_region_close_not_found: + `{0}-Tag in {1} gab "{2}" zum Einbinden der Region mit Label "{4}" aus Datei "{3}" an, aber das Kommentar zum Schließen der Region wurde nicht in der Datei gefunden.`, + include_0_tag_in_1_specified_2_file_3_region_4_region_open_not_found: + `{0}-Tag in {1} gab "{2}" zum Einbinden der Region mit Label "{4}" aus Datei "{3}" an, aber das Kommentar zum Öffnen einer Region wurde nicht in der Datei gefunden.`, + include_0_tag_in_1_specified_2_file_3_region_4_region_close_found_multiple_times: + `{0}-Tag in {1} gab "{2}" zum Einbinden der Region mit Label "{4}" aus Datei "{3}" an, aber das Kommentar zum Schließen der Region wurde mehrfach in der Datei gefunden.`, + include_0_tag_in_1_specified_2_file_3_region_4_region_open_found_multiple_times: + `{0}-Tag in {1} gab "{2}" zum Einbinden der Region mit Label "{4}" aus Datei "{3}" an, aber das Kommentar zum Öffnen der Region wurde mehrfach in der Datei gefunden.`, + include_0_tag_in_1_specified_2_file_3_region_4_region_found_multiple_times: + `{0}-Tag in {1} gab "{2}" zum Einbinden der Region mit Label "{4}" aus Datei "{3}" an, aber die Region wurde mehrfach in der Datei gefunden.`, + include_0_tag_in_1_specified_2_file_3_region_4_region_empty: + `{0}-Tag in {1} gab "{2}" zum Einbinden der Region mit Label "{4}" aus Datei "{3}" an. Die Region wurde gefunden, ist aber leer oder enthält nur Leerzeichen.`, + include_0_tag_in_1_specified_2_file_3_lines_4_invalid_range: + `{0}-Tag in {1} gab "{2}" zum Einbinden der Zeilen {4} aus Datei "{3}" an, aber ein ungültiges Intervall wurde angegeben.`, + include_0_tag_in_1_specified_2_file_3_lines_4_but_only_5_lines: + `{0}-Tag in {1} gab "{2}" zum Einbinden der Zeilen {4} aus Datei "{3}" an, aber die Datei hat nur {5} Zeilen.`, + + // output plugins + custom_css_file_0_does_not_exist: "Eigene CSS-Datei unter {0} existiert nicht", + custom_js_file_0_does_not_exist: "Eigene JavaScript-Datei unter {0} existiert nicht", + unsupported_highlight_language_0_not_highlighted_in_comment_for_1: + "Sprache {0} unterstützt keine Syntaxhervorhebung und wird im Kommentar {1} nicht hervorgehoben", + unloaded_language_0_not_highlighted_in_comment_for_1: + "Code-Block mit Sprache {0} wird keine Syntaxhervorhebung im Kommentar für {1} erfahren, da diese Sprache nicht in der Option highlightLanguages enthalten ist", + yaml_frontmatter_not_an_object: "Erwartete ein Objekt für die YAML-Frontmatter", + + // renderer + could_not_write_0: "{0} konnte nicht geschrieben werden", + could_not_empty_output_directory_0: "Ausgabeverzeichnis {0} konnte nicht geleert werden", + could_not_create_output_directory_0: "Konnte das Ausgabeverzeichnis {0} nicht erstellen", + theme_0_is_not_defined_available_are_1: `Das Theme '{0}' ist nicht definiert. Verfügbare Themes sind: {1}`, + router_0_is_not_defined_available_are_1: `Der Router '{0}' ist nicht definiert. Verfügbare Router sind: {1}`, + reflection_0_links_to_1_but_anchor_does_not_exist_try_2: + "{0} verlinkt auf {1}, aber der Anker existiert nicht. Meinten Sie vielleicht:\n\t{2}", + + // entry points + no_entry_points_provided: + "Einstiegspunkte wurden weder angegeben noch konnten sie aus den package.json-Exports ermittelt werden. Das ist wahrscheinlich eine Fehlerkonfiguration", + unable_to_find_any_entry_points: "Konnte keine Einstiegspunkte finden. Beachte auch die vorigen Warnmeldungen", + watch_does_not_support_packages_mode: "Watch-Modus unterstützt Einstiegspunkte der Art 'packages' nicht", + watch_does_not_support_merge_mode: "Watch-Modus unterstützt Einstiegspunkte der Art 'merge' nicht", + entry_point_0_not_in_program: + `Der Einstiegspunkt {0} wird nicht von der Option 'files' oder 'include' in der tsconfig referenziert`, + failed_to_resolve_0_to_ts_path: + "Konnte den Einstiegspunktpfad {0} der package.json nicht zu einer TypeScript-Quellcode-Datei auflösen", + use_expand_or_glob_for_files_in_dir: + 'Falls Sie Dateien aus diesem Verzeichnis einbinden wollten, setzen Sie die --entryPointStrategy auf "expand" oder geben Sie einen Glob an', + glob_0_did_not_match_any_files: "Der Glob {0} passte auf keine Dateien", + entry_point_0_did_not_match_any_files_after_exclude: + "Der Glob {0} passte auf keine Dateien mehr, nachdem die Exclude-Patterns angewandt wurden", + entry_point_0_did_not_exist: "Angegebener Einstiegspunkt {0} existiert nicht", + entry_point_0_did_not_match_any_packages: + "Der Einstiegspunkt-Glob {0} passte auf keine Verzeichnisse mit einer package.json-Datei", + file_0_not_an_object: "Die Datei {0} ist kein Objekt", + + // deserialization + serialized_project_referenced_0_not_part_of_project: + "Serialisiertes Projekt referenziert Reflection {0}, welche kein Teil des Projekts ist", + saved_relative_path_0_resolved_from_1_does_not_exist: + "Serialisiertes Projekt referenziert {0}, was relativ zu {1} nicht existiert", + + // options + circular_reference_extends_0: `Zyklische Referenz im "extends"-Feld von {0} gefunden`, + failed_resolve_0_to_file_in_1: "Konnte {0} in {1} nicht zu einer Datei auflösen", + + glob_0_should_use_posix_slash: + `Der Glob "{0}" maskiert nichtspezielle Zeichen. Glob-Eingaben für TypeDoc dürfen keine Windows-Pfadtrennzeichen (\\) verwenden, nutzen Sie stattdessen Posix-Pfadtrennzeichen (/)`, + option_0_can_only_be_specified_by_config_file: + `Die Option '{0}' darf nur in einer Konfigurationsdatei angegeben werden`, + option_0_expected_a_value_but_none_provided: "--{0} erwartet einen Wert, aber keiner wurde als Argument übergeben", + unknown_option_0_may_have_meant_1: "Unbekannte Option: {0}, meinten Sie vielleicht:\n\t{1}", + + typedoc_key_in_0_ignored: + `Das Feld 'typedoc' in {0} wurde von der entryPointStrategy "legacy-packages" verwendet und wird ignoriert`, + typedoc_options_must_be_object_in_0: + `Konnte das Feld "typedocOptions" in {0} nicht parsen, stellen Sie sicher, dass es existiert und ein Objekt enthält`, + tsconfig_file_0_does_not_exist: "Die tsconfig-Datei {0} existiert nicht", + tsconfig_file_specifies_options_file: + `"typedocOptions" in der tsconfig-Datei gibt eine einzulesende Datei mit Optionen an, aber die Optionsdatei wurde schon eingelesen. Das ist wahrscheinlich ein Konfigurationsfehler`, + tsconfig_file_specifies_tsconfig_file: + `"typedocOptions" in der tsconfig-Datei darf keine tsconfig-Datei zum Einlesen angeben`, + tags_0_defined_in_typedoc_json_overwritten_by_tsdoc_json: + "Die {0} aus der typedoc.json werden durch die Konfiguration in der tsdoc.json überschrieben", + failed_read_tsdoc_json_0: "Konnte tsdoc.json-Datei unter {0} nicht lesen", + invalid_tsdoc_json_0: "Die Datei {0} ist keine gültige tsdoc.json-Datei", + + options_file_0_does_not_exist: "Die Optionsdatei {0} existiert nicht", + failed_read_options_file_0: + "Konnte {0} nicht parsen, stellen Sie sicher, dass die Datei existiert und ein Objekt exportiert", + + // plugins + invalid_plugin_0_missing_load_function: "Ungültige Struktur im Plugin {0}, keine load-Funktion gefunden", + plugin_0_could_not_be_loaded: "Das Plugin {0} konnte nicht geladen werden", + + // option declarations help + help_options: + "JSON-Datei mit Optionen, die geladen werden soll. Ist keine angegeben, schaut TypeDoc nach einer 'typedoc.json' im aktuellen Verzeichnis", + help_tsconfig: + "TypeScript-Konfigurationsdatei, die geladen werden soll. Ist keine angegeben, schaut TypeDoc nach einer 'tsconfig.json' im aktuellen Verzeichnis", + help_compilerOptions: "Ausgewählte TypeScript-Compiler-Optionen überschreiben, die von TypeDoc genutzt werden", + help_lang: "Setzt die Sprache für die generierte Dokumentation und für die von TypeDoc ausgegebenen Meldungen", + help_locales: + "Fügt Übersetzungen für eine bestimmte Sprache hinzu. Die Option ist hauptsächlich als Überbrückung gedacht, bis TypeDoc die Sprache offiziell unterstützt", + help_packageOptions: + "Setzt Optionen, die innerhalb jedes Packages verwendet werden, falls die entryPointStrategy auf packages gesetzt ist", + + help_entryPoints: "Die Einstiegspunkte der Dokumentation", + help_entryPointStrategy: "Die zu nutzende Strategie, um die Einstiegspunkte in Dokumentationsmodule umzuwandeln", + help_alwaysCreateEntryPointModule: + "Falls gesetzt, erstellt TypeDoc immer ein `Modul` für Einstiegspunkte, selbst wenn nur eins angegeben wurde", + help_projectDocuments: + "Dokumente, die als Kinder zur Root-Ebene der generierten Dokumentation hinzugefügt werden sollen. Unterstützt Globs, um mehrere Dateien zu selektieren", + help_exclude: + "Patterns zum Ausschließen von Dateien, wenn nach Dateien in einem Verzeichnis gesucht wird, das als Einstiegspunkt angegeben wurde", + help_externalPattern: "Patterns für Dateien, die als extern betrachtet werden sollen", + help_excludeExternals: "Verhindert die Dokumentation von als extern aufgelösten Symbolen", + help_excludeNotDocumented: + "Verhindert, dass Symbole in der Dokumentation erscheinen, die nicht explizit dokumentiert wurden", + help_excludeNotDocumentedKinds: "Arten von Reflections, die von excludeNotDocumented entfernt werden können", + help_excludeInternal: "Verhindert, dass Symbole in der Dokumentation erscheinen, die mit @internal markiert sind", + help_excludeCategories: "Schließt Symbole aus dieser Kategorie von der Dokumentation aus", + help_excludeProtected: "Ignoriert geschützte Variablen und Methoden", + help_excludeReferences: "Wird ein Symbol mehrfach exportiert, ignoriere alle außer dem ersten Export", + help_externalSymbolLinkMappings: + "Definiert eigene Links für Symbole, die nicht in der Dokumentation enthalten sind", + help_out: + "Gibt den Pfad an, wohin die Dokumentation für die Default-Ausgabe geschrieben werden soll. Der Standard-Ausgabetyp kann von Plugins geändert werden.", + help_html: "Gibt den Pfad an, wohin die HTML-Dokumentation geschrieben werden soll.", + help_json: + "Gibt den Pfad und den Dateinamen an, wohin eine JSON-Datei mit einer Beschreibung des Projekts geschrieben werden soll", + help_pretty: "Gibt an, ob die JSON-Datei mit Tabs formatiert werden soll", + help_emit: "Gibt an, was TypeDoc ausgeben soll, 'docs', 'both', oder 'none'", + help_theme: "Gibt den Namen des Themes an, mit dem die Dokumentation erstellt werden soll", + help_router: "Gibt den Namen des Routers an, der zum Ermitteln der Dateinamen in der Dokumentation verwendet wird", + help_lightHighlightTheme: "Gibt das Theme für die Syntaxhervorhebung im Light-Modus an", + help_darkHighlightTheme: "Gibt das Theme für die Syntaxhervorhebung im Dark-Modus an", + help_highlightLanguages: "Gibt die Sprachen an, die geladen werden sollen, um Code bei der Ausgabe hervorzuheben", + help_ignoredHighlightLanguages: + "Gibt Sprachen an, welche als gültige Sprache für die Syntaxhervorhebung erkannt werden, aber zur Laufzeit nicht hervorgehoben werden", + help_typePrintWidth: "Breite beim Rendern eines Typs, ab der Code in eine neue Zeile umgebrochen wird", + help_customCss: "Pfad auf eine eigene CSS-Datei, die zusätzlich zum Theme importiert wird", + help_customJs: "Pfade auf eine eigene einzubindende JavaScript-Datei", + help_markdownItOptions: + "Gibt Optionen an, die zu markdown-it weitergereicht werden, dem von TypeDoc verwendeten Markdown-Parser", + help_markdownItLoader: + "Gibt ein Callback an, das beim Laden der markdown-it-Instanz gerufen wird. Dem Callback wird die Instanz des Parsers übergeben, den TypeDoc verwenden wird", + help_maxTypeConversionDepth: "Setzt die maximale Tiefe von Typen, bis zu der diese konvertiert werden", + help_name: "Setzt den Namen des Projekts, der im Header des Templates verwendet wird", + help_includeVersion: "Fügt die Package-Version zum Projektnamen hinzu", + help_disableSources: "Deaktiviert das Setzen der Quelle, wenn eine Reflection dokumentiert wird", + help_sourceLinkTemplate: + "Gibt ein Link-Template an, das beim Generieren von Quelldatei-URLs verwendet wird. Wenn nicht gesetzt, wird automatisch ein Template vom Git-Remote erstellt. Unterstützt die Platzhalter {path}, {line} und {gitRevision}", + help_gitRevision: + "Nutzt die angegebene Revision statt der neuesten Revision zum Verlinken der Quellcode-Dateien auf GitHub/Bitbucket. Hat keinen Effekt, wenn disableSources gesetzt ist", + help_gitRemote: + "Nutzt das angegebene Remote zum Verlinken von Quellcode-Dateien auf GitHub/Bitbucket. Hat keinen Effekt, wenn disableGit oder disableSources gesetzt ist", + help_disableGit: + "Gehe davon aus, dass auf alles mit dem sourceLinkTemplate verlinkt werden kann, sourceLinkTemplate muss gesetzt sein, falls die Option aktiviert ist. Der Platzhalter {path} ist dann relativ zum basePath", + help_basePath: "Gibt den Basispfad an, der beim Anzeigen von Dateipfaden verwendet wird", + help_excludeTags: "Entfernt die angegebenen Block- und Modifier-Tags von den Doc-Kommentaren", + help_notRenderedTags: + "Tags, die in den Doc-Kommentaren bewahrt werden, aber in der Dokumentation nicht angezeigt werden sollen", + help_cascadedModifierTags: "Modifier-Tags, die in alle Kinder einer Eltern-Reflection kopiert werden sollen", + help_readme: + "Pfad auf die Readme-Datei, die auf der Indexseite angezeigt werden soll. `none`, um die Indexseite zu deaktivieren und die Dokumentation auf der Seite mit den globalen Variablen beginnen zu lassen", + help_cname: "Setzt den CNAME-Dateitext, nützlich für eigene Domains bei GitHub-Pages", + help_favicon: "Pfad auf ein Favicon, welches als Icon für die Seite eingebunden werden soll", + help_sourceLinkExternal: + "Gibt an, dass Quelldatei-Links als externe Links behandelt und in einem neuen Tab geöffnet werden sollen", + help_markdownLinkExternal: + "Gibt an, dass http[s]://-Links in Kommentaren und Markdown-Dateien als externe Links behandelt und in einem neuen Tab geöffnet werden sollen", + help_githubPages: + "Erzeugt eine .nojekyll-Datei, um 404-Fehler bei GitHub-Pages zu vermeiden. Standardwert ist `true`", + help_hostedBaseUrl: + "Gibt die Basis-URL an, die beim Erzeugen einer sitemap.xml im Ausgabeverzeichnis und für kanonische Links verwendet wird. Wenn nicht angegeben, wird keine Sitemap erzeugt", + help_useHostedBaseUrlForAbsoluteLinks: + "Wenn gesetzt, erzeugt TypeDoc unter Verwendung der Option hostedBaseUrl absolute Links auf Unterseiten der Seite", + help_hideGenerator: "Gibt den TypeDoc-Link am Ende der Seite nicht aus", + help_customFooterHtml: "Eigener Footer nach dem TypeDoc-Link", + help_customFooterHtmlDisableWrapper: "Wenn gesetzt, wird das Wrapper-Element um customFooterHtml nicht ausgegeben", + help_cacheBust: "Zeitpunkt der Erstellung der Dokumentation in Links auf statische Assets inkludieren", + help_searchInComments: + "Wenn gesetzt, wird der Suchindex auch Kommentare enthalten. Dies wird die Größe des Suchindex stark erhöhen", + help_searchInDocuments: + "Wenn gesetzt, wird der Suchindex auch Dokumente enthalten. Dies wird die Größe des Suchindex stark erhöhen", + help_cleanOutputDir: "Wenn gesetzt, löscht TypeDoc das Ausgabeverzeichnis vor dem Schreiben der Dokumentation", + help_titleLink: + "Setzt den Link des Titels im Header. Standardmäßig wird auf die Startseite der Dokumentation verlinkt", + help_navigationLinks: "Gibt Links an, die mit in den Header geschrieben werden", + help_sidebarLinks: "Gibt Links an, die mit in die Seitenleiste geschrieben werden", + help_navigationLeaves: "Zweige des Navigationsbaums, die nicht ausgeklappt sein sollen", + help_headings: "Legt fest, welche optionalen Überschriften ausgegeben werden sollen", + help_sluggerConfiguration: "Legt fest, wie Anker im generierten HTML festgelegt werden.", + help_navigation: "Legt fest, wie die Navigationsseitenleiste organisiert wird", + help_includeHierarchySummary: + "Wenn gesetzt, wird eine Übersicht der Reflection-Hierarchie auf der Zusammenfassungsseite ausgegeben. Standardwert ist `true`", + help_visibilityFilters: + "Gibt die standardmäßige Sichtbarkeit für eingebaute Filter sowie zusätzliche Filter anhand eines Modifier-Tags an.", + help_searchCategoryBoosts: "Konfiguriert die Suche so, dass ausgewählte Kategorien als relevanter bewertet werden", + help_searchGroupBoosts: + 'Konfiguriert die Suche so, dass ausgewählte Symbolarten (z.B. "Klasse") als relevanter bewertet werden', + help_useFirstParagraphOfCommentAsSummary: + "Wenn gesetzt und kein @summary-Tag vorhanden ist, verwendet TypeDoc den ersten Absatz eines Kommentars als die Kurzzusammenfassung in der Modul- oder Namensraum-Ansicht", + help_jsDocCompatibility: + "Setzt Kompatibilitätsoptionen beim Parsen von Kommentaren, welche die Ähnlichkeit zu JSDoc-Kommentaren erhöhen", + help_suppressCommentWarningsInDeclarationFiles: + "Verhindert, dass Warnungen gemeldet werden, die durch unspezifizierte Tags innerhalb von Kommentaren in .d.ts-Dateien verursacht wurden.", + help_commentStyle: "Legt fest, wie TypeDoc nach Kommentaren sucht", + help_useTsLinkResolution: + "Verwendet TypeScripts Mechanismus zur Auflösung von Links beim Ermitteln des Ziels eines @link-Tags. Betrifft nur Kommentare im JSDoc-Stil", + help_preserveLinkText: + "Wenn gesetzt, wird bei @link-Tags ohne expliziten Link-Text der Textinhalt als Link verwendet. Wenn nicht gesetzt, wird der Name der Ziel-Reflection verwendet", + help_blockTags: "Block-Tags, die TypeDoc beim Parsen von Kommentaren erkennen soll", + help_inlineTags: "Inline-Tags, die TypeDoc beim Parsen von Kommentaren erkennen soll", + help_modifierTags: "Modifier-Tags, die TypeDoc beim Parsen von Kommentaren erkennen soll", + help_categorizeByGroup: "Gibt an, ob die Kategorisierung auf der Gruppen-Ebene vorgenommen werden soll", + help_groupReferencesByType: + "Wenn gesetzt, werden Referenzen zusammen mit dem Typ, auf den sie verweisen, gruppiert und nicht innerhalb einer 'Referenzen'-Gruppe", + help_defaultCategory: "Gibt die Standard-Kategorie für Reflections ohne eine Kategorie an", + help_categoryOrder: + "Gibt die Reihenfolge an, in der Kategorien erscheinen. * legt die relative Reihenfolge für Kategorien fest, die nicht in der Liste sind", + help_groupOrder: + "Gibt die Reihenfolge an, in der Gruppen erscheinen. * legt die relative Reihenfolge für Gruppen fest, die nicht in der Liste sind", + help_sort: "Gibt die Sortierstrategie für dokumentierte Werte an", + help_sortEntryPoints: + "Wenn gesetzt, werden auf Einstiegspunkte die gleichen Sortierregeln angewandt, die auch für andere Reflections gelten", + help_kindSortOrder: "Gibt die Sortierreihenfolge für Reflections an, wenn ein 'kind' festgelegt ist", + help_watch: "Überwache Dateien auf Änderungen und baue die Dokumentation bei Änderungen neu", + help_preserveWatchOutput: "Wenn gesetzt, leert TypeDoc den Bildschirm nicht zwischen Kompilierungsschritten", + help_skipErrorChecking: "Führt die Typenprüfung von TypeScript nicht vor Erzeugung der Dokumentation aus", + help_help: "Gibt diese Nachricht aus", + help_version: "Gibt die Version von TypeDoc aus", + help_showConfig: "Gibt die aufgelöste Konfiguration aus und stoppt", + help_plugin: + "Gibt die NPM-Plugins an, die geladen werden sollen. Nicht angeben, um alle installierten Plugins zu laden", + help_logLevel: "Gibt an, welches Level für das Logging verwendet werden soll", + help_treatWarningsAsErrors: "Wenn gesetzt, werden alle Warnungen als Fehler behandelt", + help_treatValidationWarningsAsErrors: + "Wenn gesetzt, werden alle Warnungen, die während der Validierung erzeugt wurden, als Fehler behandelt. Diese Option kann nicht zum Deaktivieren von treatWarningsAsErrors für Validierungswarnungen verwendet werden", + help_intentionallyNotExported: + "Eine Liste von Typen, welche keine Warnungen der Art 'referenziert, aber nicht dokumentiert' erzeugen sollen", + help_requiredToBeDocumented: "Eine Liste von Reflection-Arten, die dokumentiert werden müssen", + help_packagesRequiringDocumentation: "Eine Liste von Packages, die dokumentiert werden müssen", + help_intentionallyNotDocumented: + "Eine Liste von vollständigen Reflection-Namen, welche keine Warnungen erzeugen sollen, wenn sie nicht dokumentiert sind", + help_validation: "Gibt an, welche Validierungsschritte TypeDoc auf die erzeugte Dokumentation anwenden soll", + + // ================================================================== + // Option validation + // ================================================================== + unknown_option_0_you_may_have_meant_1: `Unbekannte Option '{0}'. Meinten Sie vielleicht:\n\t{1}`, + option_0_must_be_between_1_and_2: "{0} muss zwischen {1} und {2} liegen", + option_0_must_be_equal_to_or_greater_than_1: "{0} muss größer oder gleich {1} sein", + option_0_must_be_less_than_or_equal_to_1: "{0} muss kleiner oder gleich {1} sein", + option_0_must_be_one_of_1: "{0} muss enthalten sein in {1}", + flag_0_is_not_valid_for_1_expected_2: "Das Flag '{0}' ist nicht gültig für {1}, erwartet wird {2}", + expected_object_with_flag_values_for_0: "Erwartet für {0} wird entweder true/false oder ein Objekt mit Flag-Werten", + flag_values_for_0_must_be_booleans: "Flag-Werte für {0} müssen Wahrheitswerte sein", + locales_must_be_an_object: + "Die Option 'locales' muss auf ein Objekt der folgenden Form gesetzt werden: { en: { theme_implements: \"Implements\" }}", + exclude_not_documented_specified_0_valid_values_are_1: + "excludeNotDocumentedKinds erlaubt nur bekannte Werte, und ungültige Werte wurden angegeben ({0}). Die gültigen Arten sind:\n{1}", + external_symbol_link_mappings_must_be_object: + "externalSymbolLinkMappings muss vom Typ Record> sein", + highlight_theme_0_must_be_one_of_1: "{0} muss einer der folgenden Werte sein: {1}", + highlightLanguages_contains_invalid_languages_0: + "highlightLanguages enthält ungültige Sprachen: {0}, führen Sie typedoc --help aus, um eine Liste unterstützter Sprachen zu erhalten", + hostedBaseUrl_must_start_with_http: "hostedBaseUrl muss mit http:// oder https:// anfangen", + useHostedBaseUrlForAbsoluteLinks_requires_hostedBaseUrl: + "Die Option useHostedBaseUrlForAbsoluteLinks erfordert, dass auch hostedBaseUrl gesetzt wird", + favicon_must_have_one_of_the_following_extensions_0: "Favicon muss eine der folgenden Dateiendungen haben: {0}", + option_0_must_be_an_object: "Die Option '{0}' muss ein Objekt (kein Array) sein", + option_0_must_be_a_function: "Die Option '{0}' muss eine Funktion sein", + option_0_must_be_object_with_urls: "{0} muss ein Objekt sein, mit String-Labels als Schlüssel und URLs als Werte", + visibility_filters_only_include_0: "visibilityFilters darf nur die folgenden nicht-@-Schlüssel enthalten: {0}", + visibility_filters_must_be_booleans: "Alle Werte von visibilityFilters müssen Wahrheitswerte sein", + option_0_values_must_be_numbers: "Alle Werte von {0} müssen Zahlen sein", + option_0_values_must_be_array_of_tags: "{0} muss ein Array mit gültigen Tag-Namen sein", + option_0_specified_1_but_only_2_is_valid: + "{0} erlaubt nur bekannte Werte, und ungültige Werte wurden angegeben ({1}). Die gültigen Sortierungsstrategien sind:\n{2}", + option_outputs_must_be_array: + `Option "outputs" muss ein Array aus Elementen vom Typ { name: string, path: string, options?: TypeDocOptions } sein.`, + specified_output_0_has_not_been_defined: `Angegebene Ausgabe "{0}" wurde nicht definiert.`, + + // https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts + alert_note: "Hinweis", + alert_tip: "Tipp", + alert_important: "Wichtig", + alert_warning: "Warnung", + alert_caution: "Achtung", + + // ReflectionKind singular translations + kind_project: "Projekt", + kind_module: "Modul", + kind_namespace: "Namensraum", + kind_enum: "Aufzählung", + kind_enum_member: "Aufzählungselement", + kind_variable: "Variable", + kind_function: "Funktion", + kind_class: "Klasse", + kind_interface: "Schnittstelle", + kind_constructor: "Konstruktor", + kind_property: "Eigenschaft", + kind_method: "Methode", + kind_call_signature: "Aufrufsignatur", + kind_index_signature: "Indexsignatur", + kind_constructor_signature: "Konstruktorsignatur", + kind_parameter: "Parameter", + kind_type_literal: "Typenliteral", + kind_type_parameter: "Typenparameter", + kind_accessor: "Zugriffsfunktion", + kind_get_signature: "Abfragesignatur", + kind_set_signature: "Änderungssignatur", + kind_type_alias: "Typenalias", + kind_reference: "Referenz", + kind_document: "Dokument", + + // ReflectionKind plural translations + kind_plural_project: "Projekte", + kind_plural_module: "Module", + kind_plural_namespace: "Namensräume", + kind_plural_enum: "Aufzählungen", + kind_plural_enum_member: "Aufzählungselemente", + kind_plural_variable: "Variablen", + kind_plural_function: "Funktionen", + kind_plural_class: "Klassen", + kind_plural_interface: "Schnittstellen", + kind_plural_constructor: "Konstruktoren", + kind_plural_property: "Eigenschaften", + kind_plural_method: "Methoden", + kind_plural_call_signature: "Aufrufsignaturen", + kind_plural_index_signature: "Indexsignaturen", + kind_plural_constructor_signature: "Konstruktorsignaturen", + kind_plural_parameter: "Parameter", + kind_plural_type_literal: "Typenliterale", + kind_plural_type_parameter: "Typenparameter", + kind_plural_accessor: "Zugriffsfunktionen", + kind_plural_get_signature: "Abfragesignaturen", + kind_plural_set_signature: "Änderungssignaturen", + kind_plural_type_alias: "Typenaliasse", + kind_plural_reference: "Referenzen", + kind_plural_document: "Dokumente", + + // ReflectionFlag translations + flag_private: "Privat", + flag_protected: "Geschützt", + flag_public: "Öffentlich", + flag_static: "Statisch", + flag_external: "Extern", + flag_optional: "Optional", + flag_rest: "Rest", + flag_abstract: "Abstrakt", + flag_const: "Konstant", + flag_readonly: "Schreibgeschützt", + flag_inherited: "Geerbt", + + // ================================================================== + // Strings that show up in the default theme + // ================================================================== + // Page headings/labels + theme_implements: "Implementiert", + theme_indexable: "Indexierbar", + theme_type_declaration: "Typendeklaration", + theme_index: "Index", + theme_hierarchy: "Hierarchie", + theme_hierarchy_summary: "Hierarchieübersicht", + theme_hierarchy_view_summary: "Zusammenfassung anzeigen", + theme_implemented_by: "Implementiert von", + theme_defined_in: "Definiert in", + theme_implementation_of: "Implementierung von", + theme_inherited_from: "Geerbt von", + theme_overrides: "Überschreibt", + theme_returns: "Rückgabewert", + theme_generated_using_typedoc: "Generiert mit TypeDoc", // If this includes "TypeDoc", theme will insert a link at that location. + // Search + theme_preparing_search_index: "Bereite Suchindex vor...", + // Left nav bar + theme_loading: "Lade...", + // Right nav bar + theme_settings: "Einstellungen", + theme_member_visibility: "Member-Sichtbarkeit", + theme_theme: "Theme", + theme_os: "OS", + theme_light: "Light", + theme_dark: "Dark", + theme_on_this_page: "Auf dieser Seite", + + // aria-label + theme_search: "Suchen", + theme_menu: "Menu", + theme_permalink: "Permalink", + theme_folder: "Ordner", + + // Used by the frontend JS + // For the English translations only, these should also be added to + // src/lib/output/themes/default/assets/typedoc/Application.ts + // Also uses theme_folder and singular kinds + theme_copy: "Kopieren", + theme_copied: "Kopiert!", + theme_normally_hidden: "Dieser Member ist normalerweise aufgrund der Filtereinstellungen versteckt.", + theme_hierarchy_expand: "Ausklappen", + theme_hierarchy_collapse: "Einklappen", + theme_search_index_not_available: "Der Suchindex ist nicht verfügbar", + theme_search_no_results_found_for_0: "Keine Resultate gefunden für {0}", + theme_search_placeholder: "Dokumentation durchsuchen", +}); diff --git a/src/lib/internationalization/locales/en.cts b/src/lib/internationalization/locales/en.cts index eed29ca14..2244fe346 100644 --- a/src/lib/internationalization/locales/en.cts +++ b/src/lib/internationalization/locales/en.cts @@ -51,6 +51,8 @@ export = { `Converting {0} as a class requires a declaration which represents a non-type value`, converting_0_as_class_without_construct_signatures: `{0} is being converted as a class, but does not have any construct signatures`, + converting_0_as_enum_requires_value_declaration: + `Converting {0} as an enum requires a declaration which represents a non-type value`, comment_for_0_should_not_contain_block_or_modifier_tags: `The comment for {0} should not contain any block or modifier tags`, @@ -79,6 +81,8 @@ export = { "Content in the summary section will be overwritten by the @inheritDoc tag in comment at {0}", content_in_remarks_block_overwritten_by_inheritdoc_in_comment_at_0: "Content in the @remarks block will be overwritten by the @inheritDoc tag in comment at {0}", + content_in_returns_block_overwritten_by_inheritdoc_in_comment_at_0: + "Content in the @returns block will be overwritten by the @inheritDoc tag in comment at {0}", example_tag_literal_name: "The first line of an example tag will be taken literally as the example name, and should only contain text", inheritdoc_tag_properly_capitalized: "The @inheritDoc tag should be properly capitalized", @@ -91,6 +95,8 @@ export = { inline_tag_not_closed: `Inline tag is not closed`, // validation + comment_for_0_links_to_1_not_included_in_docs_use_external_link_2: + `The comment for {0} links to "{1}" which was resolved but is not included in the documentation. To fix this warning export it or add {2} to the externalSymbolLinkMappings option`, failed_to_resolve_link_to_0_in_comment_for_1: `Failed to resolve link to "{0}" in comment for {1}`, failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2: `Failed to resolve link to "{0}" in comment for {1}. You may have wanted "{2}"`, @@ -121,6 +127,8 @@ export = { `Comment for {0} includes @categoryDescription for "{1}", but no child is placed in that category`, comment_for_0_includes_groupDescription_for_1_but_no_child_in_group: `Comment for {0} includes @groupDescription for "{1}", but no child is placed in that group`, + comment_for_0_specifies_1_as_sort_strategy_but_only_2_is_valid: + `Comment for {0} specifies @sortStrategy with "{1}", which is an invalid sort strategy, the following are valid:\n\t{2}`, label_0_for_1_cannot_be_referenced: `The label "{0}" for {1} cannot be referenced with a declaration reference. Labels may only contain A-Z, 0-9, and _, and may not start with a number`, modifier_tag_0_is_mutually_exclusive_with_1_in_comment_for_2: @@ -200,7 +208,6 @@ export = { use_expand_or_glob_for_files_in_dir: `If you wanted to include files inside this directory, set --entryPointStrategy to expand or specify a glob`, glob_0_did_not_match_any_files: `The glob {0} did not match any files`, - glob_should_use_posix_slash: `Try replacing Windows path separators (\\) with posix path separators (/)`, entry_point_0_did_not_match_any_files_after_exclude: `The glob {0} did not match any files after applying exclude patterns`, entry_point_0_did_not_exist: `Provided entry point {0} does not exist`, @@ -211,13 +218,15 @@ export = { // deserialization serialized_project_referenced_0_not_part_of_project: `Serialized project referenced reflection {0}, which was not a part of the project`, - saved_relative_path_0_resolved_from_1_is_not_a_file: + saved_relative_path_0_resolved_from_1_does_not_exist: `Serialized project referenced {0}, which does not exist relative to {1}`, // options circular_reference_extends_0: `Circular reference encountered for "extends" field of {0}`, failed_resolve_0_to_file_in_1: `Failed to resolve {0} to a file in {1}`, + glob_0_should_use_posix_slash: + `The glob "{0}" escapes a non-special character. Glob inputs to TypeDoc may not use Windows path separators (\\), try replacing with posix path separators (/)`, option_0_can_only_be_specified_by_config_file: `The '{0}' option can only be specified via a config file`, option_0_expected_a_value_but_none_provided: `--{0} expected a value, but none was given as an argument`, unknown_option_0_may_have_meant_1: `Unknown option: {0}, you may have meant:\n\t{1}`, @@ -237,6 +246,7 @@ export = { options_file_0_does_not_exist: `The options file {0} does not exist`, failed_read_options_file_0: `Failed to parse {0}, ensure it exists and exports an object`, + failed_to_apply_compilerOptions_overrides_0: "Failed to apply compilerOptions overrides: {0}", // plugins invalid_plugin_0_missing_load_function: `Invalid structure in plugin {0}, no load function found`, @@ -266,7 +276,8 @@ export = { help_excludeNotDocumentedKinds: "Specify the type of reflections that can be removed by excludeNotDocumented", help_excludeInternal: "Prevent symbols that are marked with @internal from being documented", help_excludeCategories: "Exclude symbols within this category from the documentation", - help_excludePrivate: "Ignore private variables and methods, defaults to true.", + help_excludePrivate: "Ignore members marked with the private keyword and #private class fields, defaults to true.", + help_excludePrivateClassFields: "Ignore #private class fields, defaults to true.", help_excludeProtected: "Ignore protected variables and methods", help_excludeReferences: "If a symbol is exported multiple times, ignore all but the first export", help_externalSymbolLinkMappings: "Define custom links for symbols not included in the documentation", @@ -301,12 +312,15 @@ export = { "Use the specified remote for linking to GitHub/Bitbucket source files. Has no effect if disableGit or disableSources is set", help_disableGit: "Assume that all can be linked to with the sourceLinkTemplate, sourceLinkTemplate must be set if this is enabled. {path} will be rooted at basePath", - help_basePath: "Specifies the base path to be used when displaying file paths", + help_displayBasePath: + "Specifies the base path to be used when displaying file paths. If not specified, basePath is used.", help_excludeTags: "Remove the listed block/modifier tags from doc comments", help_notRenderedTags: "Tags which will be preserved in doc comments, but not rendered when creating output", help_cascadedModifierTags: "Modifier tags which should be copied to all children of the parent reflection", + help_preservedTypeAnnotationTags: "Block tags whose type annotations should be preserved in the output.", help_readme: "Path to the readme file that should be displayed on the index page. Pass `none` to disable the index page and start the documentation on the globals page", + help_basePath: "Specifies a path which links may be resolved relative to.", help_cname: "Set the CNAME file text, it's useful for custom domains on GitHub Pages", help_favicon: "Path to favicon to include as the site icon", help_sourceLinkExternal: @@ -408,6 +422,8 @@ export = { "The useHostedBaseUrlForAbsoluteLinks option requires that hostedBaseUrl be set", favicon_must_have_one_of_the_following_extensions_0: "Favicon must have one of the following extensions: {0}", option_0_must_be_an_object: "The '{0}' option must be a non-array object", + option_0_must_be_an_array_of_string: "The '{0}' option must be set to an array of strings", + option_0_must_be_an_array_of_string_or_functions: "The '{0}' option must be set to an array of strings/functions", option_0_must_be_a_function: "The '{0}' option must be a function", option_0_must_be_object_with_urls: `{0} must be an object with string labels as keys and URL values`, visibility_filters_only_include_0: `visibilityFilters can only include the following non-@ keys: {0}`, @@ -415,7 +431,7 @@ export = { option_0_values_must_be_numbers: "All values of {0} must be numbers", option_0_values_must_be_array_of_tags: "{0} must be an array of valid tag names", option_0_specified_1_but_only_2_is_valid: - `{0} may only specify known values, and invalid values were provided ({1}). The valid sort strategies are:\n{2}`, + `{0} may only specify known values, and invalid values were provided ({1}). The valid options are:\n{2}`, option_outputs_must_be_array: `"outputs" option must be an array of { name: string, path: string, options?: TypeDocOptions } values.`, specified_output_0_has_not_been_defined: `Specified output "{0}" has not been defined.`, @@ -498,7 +514,7 @@ export = { // Page headings/labels theme_implements: "Implements", theme_indexable: "Indexable", - theme_type_declaration: "Type declaration", + theme_type_declaration: "Type Declaration", theme_index: "Index", theme_hierarchy: "Hierarchy", theme_hierarchy_summary: "Hierarchy Summary", diff --git a/src/lib/internationalization/locales/ja.cts b/src/lib/internationalization/locales/ja.cts index 6d2a469b4..95667d273 100644 --- a/src/lib/internationalization/locales/ja.cts +++ b/src/lib/internationalization/locales/ja.cts @@ -138,7 +138,6 @@ export = localeUtils.buildIncompleteTranslation({ use_expand_or_glob_for_files_in_dir: "このディレクトリ内のファイルを含める場合は、--entryPointStrategyを設定して展開するか、globを指定します。", glob_0_did_not_match_any_files: "グロブ {0} はどのファイルにも一致しませんでした", - // glob_should_use_posix_slash entry_point_0_did_not_match_any_files_after_exclude: "除外パターンを適用した後、グロブ {0} はどのファイルにも一致しませんでした", entry_point_0_did_not_exist: "指定されたエントリ ポイント {0} は存在しません", @@ -147,7 +146,7 @@ export = localeUtils.buildIncompleteTranslation({ file_0_not_an_object: "ファイル {0} はオブジェクトではありません", serialized_project_referenced_0_not_part_of_project: "シリアル化されたプロジェクトは、プロジェクトの一部ではないリフレクション {0} を参照しました", - // saved_relative_path_0_resolved_from_1_is_not_a_file + // saved_relative_path_0_resolved_from_1_does_not_exist circular_reference_extends_0: '{0} の "extends" フィールドで循環参照が検出されました', failed_resolve_0_to_file_in_1: "{0} を {1} 内のファイルに解決できませんでした", option_0_can_only_be_specified_by_config_file: "'{0}' オプションは設定ファイル経由でのみ指定できます", @@ -195,7 +194,6 @@ export = localeUtils.buildIncompleteTranslation({ help_excludeNotDocumentedKinds: "excludeNotDocumented によって削除できる反射の種類を指定します", help_excludeInternal: "@internal でマークされたシンボルがドキュメント化されないようにする", help_excludeCategories: "このカテゴリ内のシンボルをドキュメントから除外する", - help_excludePrivate: "プライベート変数とメソッドを無視します。デフォルトは true です。", help_excludeProtected: "保護された変数とメソッドを無視する", help_excludeReferences: "シンボルが複数回エクスポートされた場合、最初のエクスポート以外はすべて無視されます。", help_externalSymbolLinkMappings: "ドキュメントに含まれていないシンボルのカスタムリンクを定義する", diff --git a/src/lib/internationalization/locales/ko.cts b/src/lib/internationalization/locales/ko.cts index 904ac25c2..4c62465b8 100644 --- a/src/lib/internationalization/locales/ko.cts +++ b/src/lib/internationalization/locales/ko.cts @@ -62,7 +62,6 @@ export = localeUtils.buildIncompleteTranslation({ help_excludeNotDocumentedKinds: "excludeNotDocumented로 제거될 리플렉션 유형을 지정합니다", help_excludeInternal: "@internal로 표시된 심볼이 문서화되지 않도록 방지합니다", help_excludeCategories: "문서에서 제외할 카테고리 내의 심볼을 제외합니다", - help_excludePrivate: "비공개 변수와 메서드를 무시합니다. 기본값은 true입니다.", help_excludeProtected: "보호된 변수와 메서드를 무시합니다", help_excludeReferences: "심볼이 여러 번 내보내진 경우 첫 번째 내보내기를 제외하고 모두 무시합니다", help_externalSymbolLinkMappings: "문서에 포함되지 않은 심볼에 대한 사용자 정의 링크를 정의합니다", diff --git a/src/lib/internationalization/locales/zh.cts b/src/lib/internationalization/locales/zh.cts index 162be15b6..729577b9c 100644 --- a/src/lib/internationalization/locales/zh.cts +++ b/src/lib/internationalization/locales/zh.cts @@ -79,6 +79,8 @@ export = localeUtils.buildIncompleteTranslation({ inline_tag_not_closed: "内联标签未关闭", // validation + comment_for_0_links_to_1_not_included_in_docs_use_external_link_2: + `{0} 注释中指向 “{1}” 的已解析的链接不会被包含在文档中。请将 {2} 导出或添加至 externalSymbolLinkMappings 选项以修复该警告`, failed_to_resolve_link_to_0_in_comment_for_1: "无法解析 {1} 注释中指向 “{0}” 的链接", failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2: "无法解析 {1} 的注释中指向 “{0}” 的链接。您可能想要 “{2}”", @@ -103,9 +105,11 @@ export = localeUtils.buildIncompleteTranslation({ "文档中并未使用 searchCategoryBoosts 中指定的所有类别。未使用的类别包括:\n{0}", not_all_search_group_boosts_used_0: "文档中并未使用 searchGroupBoosts 中指定的所有组。未使用的组为:\n{0}", comment_for_0_includes_categoryDescription_for_1_but_no_child_in_group: - "{0} 的评论包含“{1}”的 @categoryDescription,但该类别中没有子项", + "{0} 的注释中包含了 “{1}” 的 @categoryDescription,但该类别中没有子项", comment_for_0_includes_groupDescription_for_1_but_no_child_in_group: - "{0} 的注释包含“{1}”的 @groupDescription,但该组中没有子项", + "{0} 的注释中包含了 “{1}” 的 @groupDescription,但该分组中没有子项", + comment_for_0_specifies_1_as_sort_strategy_but_only_2_is_valid: + `{0} 的注释中指定的 “{1}” 的 @sortStrategy 无效,以下是有效的选项:\n\t{2}`, label_0_for_1_cannot_be_referenced: "无法使用声明引用来引用 {1} 的标签“{0}”。标签只能包含 A-Z、0-9 和 _,并且不能以数字开头", modifier_tag_0_is_mutually_exclusive_with_1_in_comment_for_2: "修饰符标签 {0} 与 {2} 注释中的 {1} 互斥", @@ -174,7 +178,6 @@ export = localeUtils.buildIncompleteTranslation({ failed_to_resolve_0_to_ts_path: "无法将 package.json 中的入口点 {0} 解析至 TypeScript 源文件", use_expand_or_glob_for_files_in_dir: "如果要包含此目录中的文件,请设置 --entryPointStrategy 以展开或指定 glob", glob_0_did_not_match_any_files: "glob {0} 与任何文件均不匹配", - glob_should_use_posix_slash: `请将 Windows 路径分隔符(\\)替换为 POSIX 路径分隔符(/)`, entry_point_0_did_not_match_any_files_after_exclude: "应用排除模式后,glob {0} 没有匹配任何文件", entry_point_0_did_not_exist: "提供的入口点 {0} 不存在", entry_point_0_did_not_match_any_packages: "入口点 glob {0} 与任何包含 package.json 的目录不匹配", @@ -182,12 +185,14 @@ export = localeUtils.buildIncompleteTranslation({ // deserialization serialized_project_referenced_0_not_part_of_project: "序列化项目引用了反射 {0},但它不是项目的一部分", - saved_relative_path_0_resolved_from_1_is_not_a_file: "序列化项目引用的 {0} 不存在或无法在 {1} 下找到", + saved_relative_path_0_resolved_from_1_does_not_exist: "序列化项目引用的 {0} 不存在或无法在 {1} 下找到", // options circular_reference_extends_0: "{0} 的“extends”字段出现循环引用", failed_resolve_0_to_file_in_1: "无法将 {0} 解析为 {1} 中的文件", + glob_0_should_use_posix_slash: + `该 glob “{0}” 中转义了不是特殊字符的字符。输入 TypeDoc 的 glob 可能不会使用 Windows 路径分隔符(\\),请尝试将其替换为 POSIX 路径分隔符(/)`, option_0_can_only_be_specified_by_config_file: "“{0}”选项只能通过配置文件指定", option_0_expected_a_value_but_none_provided: "--{0} 需要一个值,但没有给出任何参数", unknown_option_0_may_have_meant_1: "未知选项:{0},你可能指的是:\n\t{1}", @@ -228,7 +233,6 @@ export = localeUtils.buildIncompleteTranslation({ help_excludeNotDocumentedKinds: "指定可以通过 excludeNotDocumented 删除的反射类型", help_excludeInternal: "防止标有 @internal 的符号被记录", help_excludeCategories: "从文档中排除此类别中的符号", - help_excludePrivate: "忽略私有变量和方法,默认为 true。", help_excludeProtected: "忽略受保护的变量和方法", help_excludeReferences: "如果一个符号被导出多次,则忽略除第一次导出之外的所有导出", help_externalSymbolLinkMappings: "为文档中未包含的符号定义自定义链接", @@ -349,6 +353,8 @@ export = localeUtils.buildIncompleteTranslation({ "useHostedBaseUrlForAbsoluteLinks 选项要求设置 hostingBaseUrl", favicon_must_have_one_of_the_following_extensions_0: "favicon 的后缀名必须是下列之一:{0}", option_0_must_be_an_object: "“{0}”选项必须是非数组对象", + option_0_must_be_an_array_of_string: "“{0}”选项必须是字符串数组", + option_0_must_be_an_array_of_string_or_functions: "“{0}”选项必须是由字符串或函数构成的数组", option_0_must_be_a_function: "‘{0}’ 选项必须是一个函数", option_0_must_be_object_with_urls: "{0} 必须是具有字符串标签作为键和 URL 值的对象", visibility_filters_only_include_0: "visibilityFilters 只能包含以下非@键:{0}", @@ -515,6 +521,7 @@ export = localeUtils.buildIncompleteTranslation({ tag_return: "返回", tag_satisfies: "满足", tag_since: "添加于", + tag_sortStrategy: "排序策略", tag_template: "类型参数", tag_type: "类型", tag_typedef: "类型定义", @@ -546,6 +553,7 @@ export = localeUtils.buildIncompleteTranslation({ tag_virtual: "虚函数", tag_abstract: "抽象类", tag_class: "类", + tag_disableGroups: "禁用分组", tag_enum: "枚举", tag_event: "事件", tag_expand: "展开", diff --git a/src/lib/models/Comment.ts b/src/lib/models/Comment.ts index 0ec479eba..dea38cbf3 100644 --- a/src/lib/models/Comment.ts +++ b/src/lib/models/Comment.ts @@ -1,8 +1,9 @@ -import { assertNever, i18n, NonEnumerable, type NormalizedPath, removeIf } from "#utils"; -import type { Reflection } from "./Reflection.js"; +import { assertNever, i18n, NonEnumerable, type NormalizedPath, removeIf, type TagString } from "#utils"; +import type { Reflection, ReflectionId } from "./Reflection.js"; import { ReflectionSymbolId } from "./ReflectionSymbolId.js"; import type { Deserializer, JSONOutput, Serializer } from "#serialization"; +import type { FileId } from "./FileRegistry.js"; /** * Represents a parsed piece of a comment. @@ -34,7 +35,7 @@ export type CommentDisplayPart = */ export interface InlineTagDisplayPart { kind: "inline-tag"; - tag: `@${string}`; + tag: TagString; text: string; target?: Reflection | string | ReflectionSymbolId; tsLinkText?: string; @@ -62,7 +63,7 @@ export interface RelativeLinkDisplayPart { * A link to either some document outside of the project or a reflection. * This may be `undefined` if the relative path does not exist. */ - target: number | undefined; + target: FileId | undefined; /** * Anchor within the target page, validated after rendering if possible */ @@ -79,7 +80,7 @@ export class CommentTag { /** * The name of this tag, e.g. `@returns`, `@example` */ - tag: `@${string}`; + tag: TagString; /** * Some tags, (`@typedef`, `@param`, `@property`, etc.) may have a user defined identifier associated with them. @@ -87,6 +88,12 @@ export class CommentTag { */ name?: string; + /** + * Optional type annotation associated with this tag. TypeDoc will remove type annotations unless explicitly + * requested by the user with the `preservedTypeAnnotationTags` option. + */ + typeAnnotation?: string; + /** * The actual body text of this tag. */ @@ -102,7 +109,7 @@ export class CommentTag { /** * Create a new CommentTag instance. */ - constructor(tag: `@${string}`, text: CommentDisplayPart[]) { + constructor(tag: TagString, text: CommentDisplayPart[]) { this.tag = tag; this.content = text; } @@ -115,7 +122,7 @@ export class CommentTag { similarTo(other: CommentTag) { return ( this.tag === other.tag && - this.name === other.tag && + this.name === other.name && Comment.combineDisplayParts(this.content) === Comment.combineDisplayParts(other.content) ); @@ -129,6 +136,9 @@ export class CommentTag { if (this.name) { tag.name = this.name; } + if (this.typeAnnotation) { + tag.typeAnnotation = this.typeAnnotation; + } return tag; } @@ -137,12 +147,14 @@ export class CommentTag { tag: this.tag, name: this.name, content: Comment.serializeDisplayParts(this.content), + typeAnnotation: this.typeAnnotation, }; } fromObject(de: Deserializer, obj: JSONOutput.CommentTag) { // tag already set by Comment.fromObject this.name = obj.name; + this.typeAnnotation = obj.typeAnnotation; this.content = Comment.deserializeDisplayParts(de, obj.content); } } @@ -238,10 +250,10 @@ export class Comment { parts: JSONOutput.CommentDisplayPart[], ): CommentDisplayPart[] { const links: [ - number, + ReflectionId, InlineTagDisplayPart | RelativeLinkDisplayPart, ][] = []; - const files: [number, RelativeLinkDisplayPart][] = []; + const files: [FileId, RelativeLinkDisplayPart][] = []; const result = parts.map((part): CommentDisplayPart => { switch (part.kind) { @@ -395,7 +407,7 @@ export class Comment { /** * All modifier tags present on the comment, e.g. `@alpha`, `@beta`. */ - modifierTags: Set<`@${string}`> = new Set(); + modifierTags: Set = new Set(); /** * Label associated with this reflection, if any (https://tsdoc.org/pages/tags/label/) @@ -435,7 +447,7 @@ export class Comment { constructor( summary: CommentDisplayPart[] = [], blockTags: CommentTag[] = [], - modifierTags: Set<`@${string}`> = new Set(), + modifierTags: Set = new Set(), ) { this.summary = summary; this.blockTags = blockTags; @@ -541,15 +553,20 @@ export class Comment { } /** - * Has this comment a visible component? + * Checks if this comment contains any visible text. * - * @returns TRUE when this comment has a visible component. + * @returns TRUE when this reflection has a visible comment. */ - hasVisibleComponent(): boolean { - return ( - this.summary.some((x) => x.kind !== "text" || x.text !== "") || - this.blockTags.length > 0 - ); + hasVisibleComponent(notRenderedTags?: readonly TagString[]): boolean { + if (this.summary.some((x) => x.kind !== "text" || x.text !== "")) { + return true; + } + + if (notRenderedTags) { + return this.blockTags.some(tag => !notRenderedTags.includes(tag.tag)); + } else { + return this.blockTags.length > 0; + } } /** @@ -558,11 +575,11 @@ export class Comment { * @param tagName The name of the tag to look for. * @returns TRUE when this comment contains a tag with the given name, otherwise FALSE. */ - hasModifier(tagName: `@${string}`): boolean { + hasModifier(tagName: TagString): boolean { return this.modifierTags.has(tagName); } - removeModifier(tagName: `@${string}`) { + removeModifier(tagName: TagString) { this.modifierTags.delete(tagName); } @@ -572,18 +589,18 @@ export class Comment { * @param tagName The name of the tag to look for. * @returns The found tag or undefined. */ - getTag(tagName: `@${string}`): CommentTag | undefined { + getTag(tagName: TagString): CommentTag | undefined { return this.blockTags.find((tag) => tag.tag === tagName); } /** * Get all tags with the given tag name. */ - getTags(tagName: `@${string}`): CommentTag[] { + getTags(tagName: TagString): CommentTag[] { return this.blockTags.filter((tag) => tag.tag === tagName); } - getIdentifiedTag(identifier: string, tagName: `@${string}`) { + getIdentifiedTag(identifier: string, tagName: TagString) { return this.blockTags.find( (tag) => tag.tag === tagName && tag.name === identifier, ); @@ -593,7 +610,7 @@ export class Comment { * Removes all block tags with the given tag name from the comment. * @param tagName */ - removeTags(tagName: `@${string}`) { + removeTags(tagName: TagString) { removeIf(this.blockTags, (tag) => tag.tag === tagName); } diff --git a/src/lib/models/ContainerReflection.ts b/src/lib/models/ContainerReflection.ts index cf9835c2e..f29492f5e 100644 --- a/src/lib/models/ContainerReflection.ts +++ b/src/lib/models/ContainerReflection.ts @@ -113,6 +113,10 @@ export abstract class ContainerReflection extends Reflection { } } + override isContainer(): this is ContainerReflection { + return true; + } + override traverse(callback: TraverseCallback) { for (const child of this.children?.slice() || []) { if (callback(child, TraverseProperty.Children) === false) { diff --git a/src/lib/models/DeclarationReflection.ts b/src/lib/models/DeclarationReflection.ts index 2a2976433..50b94e773 100644 --- a/src/lib/models/DeclarationReflection.ts +++ b/src/lib/models/DeclarationReflection.ts @@ -1,5 +1,5 @@ import { type ReferenceType, ReflectionType, type SomeType } from "./types.js"; -import { type TraverseCallback, TraverseProperty } from "./Reflection.js"; +import { type ReflectionId, type TraverseCallback, TraverseProperty } from "./Reflection.js"; import { ContainerReflection } from "./ContainerReflection.js"; import type { SignatureReflection } from "./SignatureReflection.js"; import type { TypeParameterReflection } from "./TypeParameterReflection.js"; @@ -346,7 +346,7 @@ export class DeclarationReflection extends ContainerReflection { de.defer(() => { for (const [id, sid] of Object.entries(obj.symbolIdMap || {})) { const refl = this.project.getReflectionById( - de.oldIdToNewId[+id] ?? -1, + de.oldIdToNewId[+id as ReflectionId] ?? -1, ); if (refl) { this.project.registerSymbolId( @@ -354,11 +354,7 @@ export class DeclarationReflection extends ContainerReflection { new ReflectionSymbolId(sid), ); } else { - de.logger.warn( - i18n.serialized_project_referenced_0_not_part_of_project( - id.toString(), - ), - ); + de.logger.warn(i18n.serialized_project_referenced_0_not_part_of_project(id)); } } }); diff --git a/src/lib/models/DocumentReflection.ts b/src/lib/models/DocumentReflection.ts index 2cb21f2e5..d19d62b4f 100644 --- a/src/lib/models/DocumentReflection.ts +++ b/src/lib/models/DocumentReflection.ts @@ -79,6 +79,6 @@ export class DocumentReflection extends Reflection { this.content = Comment.deserializeDisplayParts(de, obj.content); this.frontmatter = obj.frontmatter; this.relevanceBoost = obj.relevanceBoost; - this.children = de.reviveMany(obj.children, (obj) => de.reflectionBuilders.document(this, obj)); + this.children = de.reviveMany(obj.children, (obj) => de.constructReflection(obj)); } } diff --git a/src/lib/models/FileRegistry.ts b/src/lib/models/FileRegistry.ts index 5b11c6737..6cda93a04 100644 --- a/src/lib/models/FileRegistry.ts +++ b/src/lib/models/FileRegistry.ts @@ -3,22 +3,24 @@ import type { ProjectReflection, Reflection } from "./index.js"; import type { ReflectionId } from "./Reflection.js"; import { type NormalizedPath, NormalizedPathUtils } from "#utils"; +export type FileId = number & { __mediaIdBrand: never }; + export class FileRegistry { protected nextId = 1; // The combination of these two make up the registry - protected mediaToReflection = new Map(); - protected mediaToPath = new Map(); + protected mediaToReflection = new Map(); + protected mediaToPath = new Map(); protected reflectionToPath = new Map(); - protected pathToMedia = new Map(); + protected pathToMedia = new Map(); // Lazily created as we get names for rendering - protected names = new Map(); + protected names = new Map(); protected nameUsage = new Map(); registerAbsolute(absolute: NormalizedPath): { - target: number; + target: FileId; anchor: string | undefined; } { const anchorIndex = absolute.indexOf("#"); @@ -27,24 +29,36 @@ export class FileRegistry { anchor = absolute.substring(anchorIndex + 1); absolute = absolute.substring(0, anchorIndex) as NormalizedPath; } - absolute = absolute.replace(/#.*/, "") as NormalizedPath; + const existing = this.pathToMedia.get(absolute); if (existing) { return { target: existing, anchor }; } - this.mediaToPath.set(this.nextId, absolute); - this.pathToMedia.set(absolute, this.nextId); + this.mediaToPath.set(this.nextId as FileId, absolute); + this.pathToMedia.set(absolute, this.nextId as FileId); - return { target: this.nextId++, anchor }; + return { target: this.nextId++ as FileId, anchor }; } + /** + * Registers the specified path as the canonical file for this reflection + */ registerReflection(absolute: NormalizedPath, reflection: Reflection) { const { target } = this.registerAbsolute(absolute); this.reflectionToPath.set(reflection.id, absolute); this.mediaToReflection.set(target, reflection.id); } + /** + * Registers the specified path as a path which should be resolved to the specified + * reflection. A reflection *may* be associated with multiple paths. + */ + registerReflectionPath(absolute: NormalizedPath, reflection: Reflection) { + const { target } = this.registerAbsolute(absolute); + this.mediaToReflection.set(target, reflection.id); + } + getReflectionPath(reflection: Reflection): string | undefined { return this.reflectionToPath.get(reflection.id); } @@ -52,7 +66,7 @@ export class FileRegistry { register( sourcePath: NormalizedPath, relativePath: NormalizedPath, - ): { target: number; anchor: string | undefined } | undefined { + ): { target: FileId; anchor: string | undefined } | undefined { return this.registerAbsolute( NormalizedPathUtils.resolve(NormalizedPathUtils.dirname(sourcePath), relativePath), ); @@ -67,17 +81,21 @@ export class FileRegistry { } resolve( - id: number, + id: FileId, project: ProjectReflection, ): string | Reflection | undefined { const reflId = this.mediaToReflection.get(id); - if (reflId) { + if (typeof reflId === "number") { return project.getReflectionById(reflId); } return this.mediaToPath.get(id); } - getName(id: number): string | undefined { + resolvePath(id: FileId): string | undefined { + return this.mediaToPath.get(id); + } + + getName(id: FileId): string | undefined { const absolute = this.mediaToPath.get(id); if (!absolute) return; @@ -137,19 +155,19 @@ export class FileRegistry { * a single object, and should merge in files from the other registries. */ fromObject(de: Deserializer, obj: JSONOutput.FileRegistry): void { - for (const [key, val] of Object.entries(obj.entries)) { - const absolute = NormalizedPathUtils.resolve(de.projectRoot, val); - de.oldFileIdToNewFileId[+key] = this.registerAbsolute(absolute).target; + for (const [fileId, path] of Object.entries(obj.entries)) { + const absolute = NormalizedPathUtils.resolve(de.projectRoot, path); + de.oldFileIdToNewFileId[+fileId as FileId] = this.registerAbsolute(absolute).target; } de.defer((project) => { - for (const [media, reflId] of Object.entries(obj.reflections)) { + for (const [fileId, reflId] of Object.entries(obj.reflections)) { const refl = project.getReflectionById( de.oldIdToNewId[reflId]!, ); if (refl) { this.mediaToReflection.set( - de.oldFileIdToNewFileId[+media]!, + de.oldFileIdToNewFileId[+fileId as FileId]!, refl.id, ); } diff --git a/src/lib/models/ProjectReflection.ts b/src/lib/models/ProjectReflection.ts index 747bf812b..68ae7586f 100644 --- a/src/lib/models/ProjectReflection.ts +++ b/src/lib/models/ProjectReflection.ts @@ -1,4 +1,4 @@ -import { type Reflection, TraverseProperty } from "./Reflection.js"; +import { type Reflection, type ReflectionId, TraverseProperty } from "./Reflection.js"; import { ContainerReflection } from "./ContainerReflection.js"; import { ReferenceReflection } from "./ReferenceReflection.js"; import type { DeclarationReflection } from "./DeclarationReflection.js"; @@ -9,11 +9,22 @@ import type { TypeParameterReflection } from "./TypeParameterReflection.js"; import { ReflectionKind } from "./kind.js"; import { Comment, type CommentDisplayPart } from "./Comment.js"; import { ReflectionSymbolId } from "./ReflectionSymbolId.js"; -import { type Deserializer, JSONOutput, type Serializer } from "#serialization"; -import { assertNever, DefaultMap, i18n, type NormalizedPath, removeIfPresent, StableKeyMap } from "#utils"; +import type { Deserializer, JSONOutput, Serializer } from "#serialization"; +import { + assertNever, + DefaultMap, + i18n, + NonEnumerable, + type NormalizedPath, + removeIfPresent, + StableKeyMap, +} from "#utils"; import type { DocumentReflection } from "./DocumentReflection.js"; import type { FileRegistry } from "./FileRegistry.js"; +// Keep this in sync with JSONOutput.SCHEMA_VERSION +export const JSON_SCHEMA_VERSION = "2.0"; + /** * A reflection that represents the root of the project. * @@ -25,18 +36,24 @@ export class ProjectReflection extends ContainerReflection { readonly variant = "project"; // Used to resolve references. + @NonEnumerable private symbolToReflectionIdMap: Map< ReflectionSymbolId, number | number[] > = new StableKeyMap(); + @NonEnumerable private reflectionIdToSymbolIdMap = new Map(); + @NonEnumerable private removedSymbolIds = new StableKeyMap(); // Maps a reflection ID to all references eventually referring to it. + @NonEnumerable private referenceGraph?: Map; + // Maps a reflection ID to all reflections with it as their parent. + @NonEnumerable private reflectionChildren = new DefaultMap(() => []); /** @@ -46,7 +63,8 @@ export class ProjectReflection extends ContainerReflection { * * This may be replaced with a `Map` someday. */ - reflections: { [id: number]: Reflection } = { [this.id]: this }; + @NonEnumerable + reflections: { [id: number]: Reflection } = {}; /** * The name of the package that this reflection documents according to package.json. @@ -350,7 +368,7 @@ export class ProjectReflection extends ContainerReflection { this.reflectionIdToSymbolIdMap.set(reflection.id, id); const previous = this.symbolToReflectionIdMap.get(id); - if (previous) { + if (typeof previous !== "undefined") { if (typeof previous === "number") { this.symbolToReflectionIdMap.set(id, [previous, reflection.id]); } else { @@ -391,7 +409,7 @@ export class ProjectReflection extends ContainerReflection { }); return { - schemaVersion: JSONOutput.SCHEMA_VERSION, + schemaVersion: JSON_SCHEMA_VERSION, ...super.toObject(serializer), variant: this.variant, packageName: this.packageName, @@ -418,15 +436,11 @@ export class ProjectReflection extends ContainerReflection { de.defer(() => { // Unnecessary conditional in release for (const [id, sid] of Object.entries(obj.symbolIdMap || {})) { - const refl = this.getReflectionById(de.oldIdToNewId[+id] ?? -1); + const refl = this.getReflectionById(de.oldIdToNewId[+id as ReflectionId] ?? -1); if (refl) { this.registerSymbolId(refl, new ReflectionSymbolId(sid)); } else { - de.logger.warn( - i18n.serialized_project_referenced_0_not_part_of_project( - id.toString(), - ), - ); + de.logger.warn(i18n.serialized_project_referenced_0_not_part_of_project(id)); } } }); diff --git a/src/lib/models/ReferenceReflection.ts b/src/lib/models/ReferenceReflection.ts index 8eb7098fa..8637970f0 100644 --- a/src/lib/models/ReferenceReflection.ts +++ b/src/lib/models/ReferenceReflection.ts @@ -1,7 +1,7 @@ import { DeclarationReflection } from "./DeclarationReflection.js"; import { ReflectionKind } from "./kind.js"; import type { Deserializer, JSONOutput, Serializer } from "#serialization"; -import type { Reflection } from "./Reflection.js"; +import type { Reflection, ReflectionId } from "./Reflection.js"; /** * Describes a reflection which does not exist at this location, but is referenced. Used for imported reflections. @@ -83,7 +83,7 @@ export class ReferenceReflection extends DeclarationReflection { return { ...super.toObject(serializer), variant: this.variant, - target: this.tryGetTargetReflection()?.id ?? -1, + target: this.tryGetTargetReflection()?.id ?? -1 as ReflectionId, }; } diff --git a/src/lib/models/Reflection.ts b/src/lib/models/Reflection.ts index 62d7438c5..a854fcd62 100644 --- a/src/lib/models/Reflection.ts +++ b/src/lib/models/Reflection.ts @@ -1,7 +1,7 @@ import { Comment } from "./Comment.js"; import { splitUnquotedString } from "./utils.js"; import type { ProjectReflection } from "./ProjectReflection.js"; -import { i18n, type NeverIfInternal, NonEnumerable, type TranslatedString } from "#utils"; +import { i18n, type NeverIfInternal, NonEnumerable, type TagString, type TranslatedString } from "#utils"; import { ReflectionKind } from "./kind.js"; import type { Deserializer, JSONOutput, Serializer } from "#serialization"; import type { ReflectionVariant } from "./variant.js"; @@ -11,6 +11,7 @@ import type { ParameterReflection } from "./ParameterReflection.js"; import type { ReferenceReflection } from "./ReferenceReflection.js"; import type { SignatureReflection } from "./SignatureReflection.js"; import type { TypeParameterReflection } from "./TypeParameterReflection.js"; +import type { ContainerReflection } from "./ContainerReflection.js"; /** * Current reflection id. @@ -265,6 +266,10 @@ export type ReflectionVisitor = { [K in keyof ReflectionVariant]?: (refl: ReflectionVariant[K]) => void; }; +/** + * Alias for a `number` which references a reflection. + * `-1` may be used for an invalid reflection ID. + */ export type ReflectionId = number & { __reflectionIdBrand: never }; /** @@ -387,12 +392,12 @@ export abstract class Reflection { } /** - * Has this reflection a visible comment? + * Checks if this reflection has a comment which contains any visible text. * * @returns TRUE when this reflection has a visible comment. */ - hasComment(): boolean { - return this.comment ? this.comment.hasVisibleComponent() : false; + hasComment(notRenderedTags?: readonly TagString[]): boolean { + return this.comment ? this.comment.hasVisibleComponent(notRenderedTags) : false; } hasGetterOrSetter(): boolean { @@ -451,6 +456,9 @@ export abstract class Reflection { isReference(): this is ReferenceReflection { return this.variant === "reference"; } + isContainer(): this is ContainerReflection { + return false; + } /** * Check if this reflection or any of its parents have been marked with the `@deprecated` tag. diff --git a/src/lib/models/index.ts b/src/lib/models/index.ts index 5788dae60..f54c3662f 100644 --- a/src/lib/models/index.ts +++ b/src/lib/models/index.ts @@ -5,7 +5,7 @@ export * from "./DocumentReflection.js"; export * from "./FileRegistry.js"; export * from "./kind.js"; export * from "./ParameterReflection.js"; -export * from "./ProjectReflection.js"; +export { ProjectReflection } from "./ProjectReflection.js"; export * from "./ReferenceReflection.js"; export * from "./Reflection.js"; export * from "./ReflectionCategory.js"; diff --git a/src/lib/models/types.ts b/src/lib/models/types.ts index f1eaac8e5..b7cf99729 100644 --- a/src/lib/models/types.ts +++ b/src/lib/models/types.ts @@ -1,4 +1,4 @@ -import type { Reflection } from "./Reflection.js"; +import type { Reflection, ReflectionId } from "./Reflection.js"; import type { DeclarationReflection } from "./DeclarationReflection.js"; import type { ProjectReflection } from "./ProjectReflection.js"; import type { Deserializer, JSONOutput, Serializer } from "#serialization"; @@ -6,7 +6,7 @@ import { ReflectionSymbolId } from "./ReflectionSymbolId.js"; import type { DeclarationReference } from "#utils"; import { ReflectionKind } from "./kind.js"; import { Comment, type CommentDisplayPart } from "./Comment.js"; -import { i18n, joinArray } from "#utils"; +import { i18n, joinArray, NonEnumerable } from "#utils"; import type { SignatureReflection } from "./SignatureReflection.js"; /** @@ -916,12 +916,13 @@ export class ReferenceType extends Type { */ preferValues = false; - private _target: ReflectionSymbolId | number; + private _target: ReflectionSymbolId | ReflectionId; + @NonEnumerable private _project: ProjectReflection | null; private constructor( name: string, - target: ReflectionSymbolId | Reflection | number, + target: ReflectionSymbolId | Reflection | ReflectionId, project: ProjectReflection | null, qualifiedName: string, ) { @@ -947,7 +948,7 @@ export class ReferenceType extends Type { static createResolvedReference( name: string, - target: Reflection | number, + target: Reflection | ReflectionId, project: ProjectReflection | null, ) { return new ReferenceType(name, target, project, name); @@ -959,8 +960,10 @@ export class ReferenceType extends Type { * later during conversion. * @internal */ - static createBrokenReference(name: string, project: ProjectReflection) { - return new ReferenceType(name, -1, project, name); + static createBrokenReference(name: string, project: ProjectReflection, packageName: string | undefined) { + const ref = new ReferenceType(name, -1 as ReflectionId, project, name); + ref.package = packageName; + return ref; } protected override getTypeString() { @@ -987,7 +990,7 @@ export class ReferenceType extends Type { if (typeof this._target === "number") { target = this._target; } else if (this._project?.symbolIdHasBeenRemoved(this._target)) { - target = -1; + target = -1 as ReflectionId; } else if (this.reflection) { target = this.reflection.id; } else { @@ -1034,24 +1037,26 @@ export class ReferenceType extends Type { override fromObject(de: Deserializer, obj: JSONOutput.ReferenceType): void { this.typeArguments = de.reviveMany(obj.typeArguments, (t) => de.constructType(t)); - if (typeof obj.target === "number" && obj.target !== -1) { - de.defer((project) => { - const target = project.getReflectionById( - de.oldIdToNewId[obj.target as number] ?? -1, - ); - if (target) { - this._project = project; - this._target = target.id; - } else { - de.logger.warn( - i18n.serialized_project_referenced_0_not_part_of_project( - JSON.stringify(obj.target), - ), + if (typeof obj.target === "number") { + if (obj.target === -1) { + this._target = -1 as ReflectionId; + } else { + de.defer((project) => { + const target = project.getReflectionById( + de.oldIdToNewId[obj.target as ReflectionId] ?? -1, ); - } - }); - } else if (obj.target === -1) { - this._target = -1; + if (target) { + this._project = project; + this._target = target.id; + } else { + de.logger.warn( + i18n.serialized_project_referenced_0_not_part_of_project( + JSON.stringify(obj.target), + ), + ); + } + }); + } } else { this._project = de.project!; this._target = new ReflectionSymbolId(obj.target); diff --git a/src/lib/output/formatter.tsx b/src/lib/output/formatter.tsx index 8015cb002..ba2f874b1 100644 --- a/src/lib/output/formatter.tsx +++ b/src/lib/output/formatter.tsx @@ -961,6 +961,7 @@ export class FormattedCodeBuilder { const id = this.newId(); return group(id, [ name, + sig.parent.flags.isOptional ? simpleElement(?) : emptyNode, this.typeParameters(sig), ...this.parameters(sig, id), nodes( diff --git a/src/lib/output/plugins/AssetsPlugin.ts b/src/lib/output/plugins/AssetsPlugin.ts index f477677a7..71b53d35b 100644 --- a/src/lib/output/plugins/AssetsPlugin.ts +++ b/src/lib/output/plugins/AssetsPlugin.ts @@ -1,6 +1,6 @@ import { RendererComponent } from "../components.js"; import { RendererEvent } from "../events.js"; -import { copySync, readFile, writeFileSync } from "../../utils/fs.js"; +import { copySync, isFile, readFile, writeFileSync } from "../../utils/fs.js"; import { DefaultTheme } from "../themes/default/DefaultTheme.js"; import { getStyles } from "../../utils/highlighter.js"; import { type EnumKeys, getEnumKeys, i18n, type NormalizedPath } from "#utils"; @@ -123,7 +123,13 @@ export class AssetsPlugin extends RendererComponent { const media = join(event.outputDirectory, "media"); const toCopy = event.project.files.getNameToAbsoluteMap(); for (const [fileName, absolute] of toCopy.entries()) { - copySync(absolute, join(media, fileName)); + if (isFile(absolute)) { + copySync(absolute, join(media, fileName)); + } else { + this.application.logger.warn( + i18n.relative_path_0_is_not_a_file_and_will_not_be_copied_to_output(absolute), + ); + } } } } diff --git a/src/lib/output/themes/MarkedPlugin.tsx b/src/lib/output/themes/MarkedPlugin.tsx index 9df6941df..d1b636206 100644 --- a/src/lib/output/themes/MarkedPlugin.tsx +++ b/src/lib/output/themes/MarkedPlugin.tsx @@ -87,22 +87,22 @@ export class MarkedPlugin extends ContextAwareRendererComponent { public getHighlighted(text: string, lang?: string): string { lang = lang || "typescript"; lang = lang.toLowerCase(); - if (!isSupportedLanguage(lang)) { - this.application.logger.warn( - i18n.unsupported_highlight_language_0_not_highlighted_in_comment_for_1( - lang, - getFriendlyFullName(this.page?.model || { name: "(unknown)" }), - ), - ); - return text; - } if (!isLoadedLanguage(lang)) { - this.application.logger.warn( - i18n.unloaded_language_0_not_highlighted_in_comment_for_1( - lang, - getFriendlyFullName(this.page?.model || { name: "(unknown)" }), - ), - ); + if (isSupportedLanguage(lang)) { + this.application.logger.warn( + i18n.unloaded_language_0_not_highlighted_in_comment_for_1( + lang, + getFriendlyFullName(this.page?.model || { name: "(unknown)" }), + ), + ); + } else { + this.application.logger.warn( + i18n.unsupported_highlight_language_0_not_highlighted_in_comment_for_1( + lang, + getFriendlyFullName(this.page?.model || { name: "(unknown)" }), + ), + ); + } return text; } @@ -195,6 +195,12 @@ export class MarkedPlugin extends ContextAwareRendererComponent { ); } } + + // If the url goes to this page, render as `#` + // to go to the top of the page. + if (url == "") { + url = "#"; + } } if (useHtml) { @@ -226,7 +232,16 @@ export class MarkedPlugin extends ContextAwareRendererComponent { const refl = page.project.files.resolve(part.target, page.model.project); let url: string | undefined; if (typeof refl === "object") { - url = context.urlTo(refl); + // #3006, this is an unfortunate heuristic. If there is a relative link to the project + // the user probably created it by linking to the directory of the project or to + // the project's readme. Since the readme doesn't get its own reflection, we can't + // reliably disambiguate this and instead will arbitrarily decide to reference the + // root index page in this case. + if (refl.isProject()) { + url = context.relativeURL("./"); + } else { + url = context.urlTo(refl); + } } else { const fileName = page.project.files.getName(part.target); if (fileName) { @@ -394,10 +409,19 @@ export class MarkedPlugin extends ContextAwareRendererComponent { } function getTokenTextContent(token: md.Token): string { + // If there are children, we want their text content, not the full text content if (token.children) { return token.children.map(getTokenTextContent).join(""); } - return token.content; + + // If this is a simple text fragment, use its content + if (token.type === "text") { + return token.content; + } + + // Otherwise this is some type of metadata token (e.g. header_open) + // or a HTML tag token. Don't include it in the text content. + return ""; } const kindNames = ["note", "tip", "important", "warning", "caution"]; diff --git a/src/lib/output/themes/default/DefaultTheme.tsx b/src/lib/output/themes/default/DefaultTheme.tsx index 3c699240a..10a2c5c99 100644 --- a/src/lib/output/themes/default/DefaultTheme.tsx +++ b/src/lib/output/themes/default/DefaultTheme.tsx @@ -15,7 +15,7 @@ import type { PageEvent, RendererEvent } from "../../events.js"; import type { MarkedPlugin } from "../../plugins/index.js"; import { DefaultThemeRenderContext } from "./DefaultThemeRenderContext.js"; import { getIcons, type Icons } from "./partials/icon.js"; -import { filterMap, JSX } from "#utils"; +import { filterMap, JSX, type TagString } from "#utils"; import { classNames, getDisplayName, toStyleClass } from "../lib.js"; import { PageKind, type Router } from "../../router.js"; import { loadHighlighter, Option } from "#node-utils"; @@ -365,8 +365,8 @@ function getReflectionClasses(reflection: Reflection, filters: Record - refl.comment?.hasModifier(key as `@${string}`) || refl.comment?.getTag(key as `@${string}`), + (refl) => refl.comment?.hasModifier(key as TagString) || refl.comment?.getTag(key as TagString), ) ) { classes.add(toStyleClass(`tsd-is-${key.substring(1)}`)); diff --git a/src/lib/output/themes/default/Slugger.ts b/src/lib/output/themes/default/Slugger.ts index 65632cd28..2198ae58e 100644 --- a/src/lib/output/themes/default/Slugger.ts +++ b/src/lib/output/themes/default/Slugger.ts @@ -1,5 +1,5 @@ import { getSimilarValues } from "#utils"; -import type { TypeDocOptionMap } from "../../../utils/index.js"; +import type { TypeDocOptionMap } from "#node-utils"; /** * Responsible for getting a unique anchor for elements within a page. @@ -8,20 +8,28 @@ export class Slugger { private seen = new Map(); private serialize(value: string) { - // Notes: - // There are quite a few trade-offs here. + // There are quite a few trade-offs here. We used to remove HTML tags here, + // but TypeDoc now removes the HTML tags before passing text into the slug + // method, which allows us to skip doing that here. This improves the slugger + // generation for headers which look like the following: + // (html allowed in markdown) + // # test <t> + // (html disallowed in markdown) + // # test + // both of the above should slug to test-t return ( value .trim() - // remove html tags - .replace(/<[!/a-z].*?>/gi, "") // remove unwanted chars .replace( /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, "", ) + // change whitespace to dash .replace(/\s/g, "-") + // combine adjacent dashes + .replace(/--+/, "-") ); } diff --git a/src/lib/output/themes/default/assets/typedoc/Navigation.ts b/src/lib/output/themes/default/assets/typedoc/Navigation.ts index cf1d3b05a..253469879 100644 --- a/src/lib/output/themes/default/assets/typedoc/Navigation.ts +++ b/src/lib/output/themes/default/assets/typedoc/Navigation.ts @@ -110,12 +110,30 @@ function addNavText( el.icon || el.kind }">`; } - a.appendChild(document.createElement("span")).textContent = el.text; + a.appendChild(wbr(el.text, document.createElement("span"))); } else { const span = parent.appendChild(document.createElement("span")); const label = window.translations.folder.replaceAll('"', """); span.innerHTML = ``; - span.appendChild(document.createElement("span")).textContent = el.text; + span.appendChild(wbr(el.text, document.createElement("span"))); } } + +function wbr(str: string, element: HTMLElement) { + // Keep this in sync with the same helper in lib.tsx + // We use lookahead/lookbehind to indicate where the string should + // be split without consuming a character. + // (?<=[^A-Z])(?=[A-Z]) -- regular camel cased text + // (?<=[A-Z])(?=[A-Z][a-z]) -- acronym + // (?<=[_-])(?=[^_-]) -- snake + const parts = str.split(/(?<=[^A-Z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[_-])(?=[^_-])/); + for (let i = 0; i < parts.length; ++i) { + if (i !== 0) { + element.appendChild(document.createElement("wbr")); + } + element.appendChild(document.createTextNode(parts[i])); + } + + return element; +} diff --git a/src/lib/output/themes/default/partials/comment.tsx b/src/lib/output/themes/default/partials/comment.tsx index 1f0109381..dc7a4d128 100644 --- a/src/lib/output/themes/default/partials/comment.tsx +++ b/src/lib/output/themes/default/partials/comment.tsx @@ -69,30 +69,35 @@ export function commentTags(context: DefaultThemeRenderContext, props: Reflectio skipSave.forEach((skip, i) => (props.comment!.blockTags[i].skipRendering = skip)); + const tagsContents = tags.map((item) => { + const name = item.name + ? `${translateTagName(item.tag)}: ${item.name}` + : translateTagName(item.tag); + + const anchor = context.slugger.slug(name); + + return ( + <> +
+ + {item.typeAnnotation && {item.typeAnnotation}} + +
+ + ); + }); + return ( <> {beforeTags} -
- {tags.map((item) => { - const name = item.name - ? `${translateTagName(item.tag)}: ${item.name}` - : translateTagName(item.tag); - - const anchor = context.slugger.slug(name); - - return ( - <> -
- - -
- - ); - })} -
+ {tagsContents.length > 0 && ( +
+ {tagsContents} +
+ )} {afterTags} ); diff --git a/src/lib/output/themes/default/partials/icon.tsx b/src/lib/output/themes/default/partials/icon.tsx index 27a415cbe..91ce4875d 100644 --- a/src/lib/output/themes/default/partials/icon.tsx +++ b/src/lib/output/themes/default/partials/icon.tsx @@ -61,6 +61,7 @@ export interface Icons extends Record JSX.Element> { checkbox(): JSX.Element; menu(): JSX.Element; search(): JSX.Element; + /** @deprecated */ chevronSmall(): JSX.Element; anchor(): JSX.Element; folder(): JSX.Element; diff --git a/src/lib/output/themes/default/partials/index.tsx b/src/lib/output/themes/default/partials/index.tsx index 6f878365a..8c1f03236 100644 --- a/src/lib/output/themes/default/partials/index.tsx +++ b/src/lib/output/themes/default/partials/index.tsx @@ -45,7 +45,7 @@ export function index(context: DefaultThemeRenderContext, props: ContainerReflec
- {context.icons.chevronSmall()} + {context.icons.chevronDown()}
{i18n.theme_index()}
diff --git a/src/lib/output/themes/default/partials/member.declaration.tsx b/src/lib/output/themes/default/partials/member.declaration.tsx index 4086ebcb8..510caa883 100644 --- a/src/lib/output/themes/default/partials/member.declaration.tsx +++ b/src/lib/output/themes/default/partials/member.declaration.tsx @@ -1,9 +1,28 @@ -import type { DeclarationReflection } from "../../../../models/index.js"; +import type { DeclarationReflection } from "#models"; import { JSX } from "#utils"; import { FormattedCodeBuilder, FormattedCodeGenerator, type FormatterNode, Wrap } from "../../../formatter.js"; import { hasTypeParameters } from "../../lib.js"; import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext.js"; +function shouldRenderDefaultValue(props: DeclarationReflection) { + const defaultValue = props.defaultValue; + + if (defaultValue === undefined) { + return false; + } + + /** Fix for #2717. If type is the same as value the default value is omitted */ + if (props.type && props.type.type === "literal") { + const reflectionTypeString = props.type.toString(); + + if (reflectionTypeString === defaultValue) { + return false; + } + } + + return true; +} + export function memberDeclaration(context: DefaultThemeRenderContext, props: DeclarationReflection) { const builder = new FormattedCodeBuilder(context.router, context.model); const content: FormatterNode[] = []; @@ -11,31 +30,15 @@ export function memberDeclaration(context: DefaultThemeRenderContext, props: Dec const generator = new FormattedCodeGenerator(context.options.getValue("typePrintWidth")); generator.node({ type: "nodes", content }, Wrap.Detect); - /** Fix for #2717. If type is the same as value the default value is omitted */ - function shouldRenderDefaultValue() { - if (props.type && props.type.type === "literal") { - const reflectionTypeString = props.type.toString(); - - const defaultValue = props.defaultValue; - - if (defaultValue === undefined || reflectionTypeString === defaultValue.toString()) { - return false; - } - } - return true; - } - return ( <>
{generator.toElement()} - {!!props.defaultValue && shouldRenderDefaultValue() && ( - <> - - {" = "} - {props.defaultValue} - - + {shouldRenderDefaultValue(props) && ( + + {" = "} + {props.defaultValue} + )}
diff --git a/src/lib/output/themes/default/partials/member.signature.body.tsx b/src/lib/output/themes/default/partials/member.signature.body.tsx index f22486924..627721300 100644 --- a/src/lib/output/themes/default/partials/member.signature.body.tsx +++ b/src/lib/output/themes/default/partials/member.signature.body.tsx @@ -25,7 +25,7 @@ export function memberSignatureBody(
  • {context.reflectionFlags(item)} - {!!item.flags.isRest && ...} + {item.flags.isRest && ...} {item.name} {": "} {context.type(item.type)} diff --git a/src/lib/output/themes/default/partials/moduleReflection.tsx b/src/lib/output/themes/default/partials/moduleReflection.tsx index 9c0af4d77..89a4dd4d9 100644 --- a/src/lib/output/themes/default/partials/moduleReflection.tsx +++ b/src/lib/output/themes/default/partials/moduleReflection.tsx @@ -16,14 +16,14 @@ export function moduleReflection(context: DefaultThemeRenderContext, mod: Declar return ( <> - {mod.hasComment() && ( + {mod.hasComment(context.options.getValue("notRenderedTags")) && (
    {context.commentSummary(mod)} {context.commentTags(mod)}
    )} - {mod.isDeclaration() && mod.kind === ReflectionKind.Module && mod.readme?.length && ( + {mod.isDeclaration() && mod.kind === ReflectionKind.Module && !!mod.readme?.length && (
    @@ -75,7 +75,7 @@ export function moduleMemberSummary( context: DefaultThemeRenderContext, member: DeclarationReflection | DocumentReflection, ) { - const id = context.slugger.slug(member.name); + const id = member.isReference() ? context.getAnchor(member)! : context.slugger.slug(member.name); context.page.pageHeadings.push({ link: `#${id}`, text: getDisplayName(member), diff --git a/src/lib/output/themes/default/partials/navigation.tsx b/src/lib/output/themes/default/partials/navigation.tsx index d53bf60ad..39e9efa93 100644 --- a/src/lib/output/themes/default/partials/navigation.tsx +++ b/src/lib/output/themes/default/partials/navigation.tsx @@ -1,5 +1,5 @@ import { type Reflection, ReflectionFlag, ReflectionFlags } from "../../../../models/index.js"; -import { i18n, JSX, translateTagName } from "#utils"; +import { i18n, JSX, type TagString, translateTagName } from "#utils"; import type { PageEvent, PageHeading } from "../../../events.js"; import { classNames, getDisplayName, wbr } from "../../lib.js"; import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext.js"; @@ -65,7 +65,7 @@ export function settings(context: DefaultThemeRenderContext) { buildFilterItem( context, filterName, - translateTagName(key as `@${string}`), + translateTagName(key as TagString), defaultFilters[key], ), ); @@ -97,7 +97,7 @@ export function settings(context: DefaultThemeRenderContext) {
  • - {visibilityOptions.length && ( + {!!visibilityOptions.length && (
    {i18n.theme_member_visibility()}
      {...visibilityOptions}
    diff --git a/src/lib/output/themes/default/partials/typeAndParent.tsx b/src/lib/output/themes/default/partials/typeAndParent.tsx index 3cac22a88..18cfb10d1 100644 --- a/src/lib/output/themes/default/partials/typeAndParent.tsx +++ b/src/lib/output/themes/default/partials/typeAndParent.tsx @@ -1,5 +1,5 @@ import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext.js"; -import { ArrayType, ReferenceType, SignatureReflection, type Type } from "../../../../models/index.js"; +import { ArrayType, ReferenceType, SignatureReflection, type Type } from "#models"; import { JSX } from "#utils"; export const typeAndParent = (context: DefaultThemeRenderContext, props: Type): JSX.Element => { @@ -12,15 +12,23 @@ export const typeAndParent = (context: DefaultThemeRenderContext, props: Type): ); } - if (props instanceof ReferenceType && props.reflection) { - const refl = props.reflection instanceof SignatureReflection ? props.reflection.parent : props.reflection; - const parent = refl.parent!; + if (props instanceof ReferenceType) { + if (props.reflection) { + const refl = props.reflection instanceof SignatureReflection ? props.reflection.parent : props.reflection; + const parent = refl.parent!; - return ( - <> - {
    {parent.name}}.{{refl.name}} - - ); + return ( + <> + {{parent.name}}.{{refl.name}} + + ); + } else if (props.externalUrl) { + if (props.externalUrl === "#") { + return <>{props.toString()}; + } else { + return {props.name}; + } + } } return <>{props.toString()}; diff --git a/src/lib/output/themes/default/partials/typeDetails.tsx b/src/lib/output/themes/default/partials/typeDetails.tsx index d0c1e1e99..829a81de8 100644 --- a/src/lib/output/themes/default/partials/typeDetails.tsx +++ b/src/lib/output/themes/default/partials/typeDetails.tsx @@ -7,24 +7,29 @@ import { type SignatureReflection, } from "../../../../models/index.js"; import type { ReferenceType, SomeType, TypeVisitor } from "../../../../models/types.js"; -import { assert, i18n, JSX } from "#utils"; +import { assert, i18n, JSX, type TagString } from "#utils"; import { classNames, getKindClass } from "../../lib.js"; import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext.js"; import { anchorTargetIfPresent } from "./anchor-icon.js"; -function renderingTypeDetailsIsUseful(container: Reflection, type: SomeType): boolean { +function renderingTypeDetailsIsUseful( + container: Reflection, + type: SomeType, + notRenderedTags: readonly TagString[], +): boolean { const isUsefulVisitor: Partial> = { array(type) { - return renderingTypeDetailsIsUseful(container, type.elementType); + return renderingTypeDetailsIsUseful(container, type.elementType, notRenderedTags); }, intersection(type) { - return type.types.some(t => renderingTypeDetailsIsUseful(container, t)); + return type.types.some(t => renderingTypeDetailsIsUseful(container, t, notRenderedTags)); }, union(type) { - return !!type.elementSummaries || type.types.some(t => renderingTypeDetailsIsUseful(container, t)); + return !!type.elementSummaries || + type.types.some(t => renderingTypeDetailsIsUseful(container, t, notRenderedTags)); }, reflection(type) { - return renderingChildIsUseful(type.declaration); + return renderingChildIsUseful(type.declaration, notRenderedTags); }, reference(type) { return shouldExpandReference(container, type); @@ -44,7 +49,7 @@ export function typeDeclaration( "typeDeclaration(reflectionOwningType, type) called incorrectly", ); - if (renderingTypeDetailsIsUseful(reflectionOwningType, type)) { + if (renderingTypeDetailsIsUseful(reflectionOwningType, type, context.options.getValue("notRenderedTags"))) { return (

    {i18n.theme_type_declaration()}

    @@ -194,7 +199,7 @@ export function typeDetailsIfUseful( "typeDetailsIfUseful(reflectionOwningType, type) called incorrectly", ); - if (type && renderingTypeDetailsIsUseful(reflectionOwningType, type)) { + if (type && renderingTypeDetailsIsUseful(reflectionOwningType, type, context.options.getValue("notRenderedTags"))) { return context.typeDetails(reflectionOwningType, type, false); } } @@ -290,9 +295,9 @@ function renderChild( return (
  • - {!!child.flags.isRest && ...} + {child.flags.isRest && ...} {child.name} - {!!child.flags.isOptional && "?"}: function + {child.flags.isOptional && "?"}: function
    {context.memberSignatures(child)} @@ -314,20 +319,22 @@ function renderChild( // standard type if (child.type) { + const notRenderedTags = context.options.getValue("notRenderedTags"); + return (
  • {context.reflectionFlags(child)} - {!!child.flags.isRest && ...} + {child.flags.isRest && ...} {child.name} - {!!child.flags.isOptional && "?"} + {child.flags.isOptional && "?"} {": "} {context.type(child.type)}
    {highlightOrComment(child)} - {child.getProperties().some(renderingChildIsUseful) && ( + {child.getProperties().some(prop => renderingChildIsUseful(prop, notRenderedTags)) && (
      {child.getProperties().map((c) => renderChild(context, c, renderAnchors))}
    @@ -401,7 +408,7 @@ function renderIndexSignature(context: DefaultThemeRenderContext, index: Signatu ); } -function renderingChildIsUseful(refl: DeclarationReflection) { +function renderingChildIsUseful(refl: DeclarationReflection, notRenderedTags: readonly TagString[]) { // Object types directly under a variable/type alias will always be considered useful. // This probably isn't ideal, but it is an easy thing to check when assigning URLs // in the default theme, so we'll make the assumption that those properties ought to always @@ -415,20 +422,20 @@ function renderingChildIsUseful(refl: DeclarationReflection) { return true; } - if (renderingThisChildIsUseful(refl)) { + if (renderingThisChildIsUseful(refl, notRenderedTags)) { return true; } - return refl.getProperties().some(renderingThisChildIsUseful); + return refl.getProperties().some(prop => renderingThisChildIsUseful(prop, notRenderedTags)); } -function renderingThisChildIsUseful(refl: DeclarationReflection) { - if (refl.hasComment()) return true; +function renderingThisChildIsUseful(refl: DeclarationReflection, notRenderedTags: readonly TagString[]) { + if (refl.hasComment(notRenderedTags)) return true; const declaration = refl.type?.type === "reflection" ? refl.type.declaration : refl; - if (declaration.hasComment()) return true; + if (declaration.hasComment(notRenderedTags)) return true; return declaration.getAllSignatures().some((sig) => { - return sig.hasComment() || sig.parameters?.some((p) => p.hasComment()); + return sig.hasComment(notRenderedTags) || sig.parameters?.some((p) => p.hasComment(notRenderedTags)); }); } diff --git a/src/lib/output/themes/default/templates/hierarchy.tsx b/src/lib/output/themes/default/templates/hierarchy.tsx index fcc9091f2..7f66e7472 100644 --- a/src/lib/output/themes/default/templates/hierarchy.tsx +++ b/src/lib/output/themes/default/templates/hierarchy.tsx @@ -37,7 +37,7 @@ function fullHierarchy( {context.reflectionIcon(root)} {root.name} - {children.length &&
      {children}
    } + {!!children.length &&
      {children}
    }
  • ); } diff --git a/src/lib/output/themes/default/templates/reflection.tsx b/src/lib/output/themes/default/templates/reflection.tsx index 6c949995d..2fd4735a3 100644 --- a/src/lib/output/themes/default/templates/reflection.tsx +++ b/src/lib/output/themes/default/templates/reflection.tsx @@ -27,7 +27,7 @@ export function reflectionTemplate(context: DefaultThemeRenderContext, props: Pa return ( <> - {props.model.hasComment() && ( + {props.model.hasComment(context.options.getValue("notRenderedTags")) && (
    {context.commentSummary(props.model)} {context.commentTags(props.model)} diff --git a/src/lib/output/themes/lib.tsx b/src/lib/output/themes/lib.tsx index 7a930a9bf..f9aa0938d 100644 --- a/src/lib/output/themes/lib.tsx +++ b/src/lib/output/themes/lib.tsx @@ -49,18 +49,14 @@ export function getKindClass(refl: Reflection): string { * @return The original string containing ```` tags where possible. */ export function wbr(str: string): (string | JSX.Element)[] { - // TODO surely there is a better way to do this, but I'm tired. - const ret: (string | JSX.Element)[] = []; - const re = /[\s\S]*?(?:[^_-][_-](?=[^_-])|[^A-Z](?=[A-Z][^A-Z]))/g; - let match: RegExpExecArray | null; - let i = 0; - while ((match = re.exec(str))) { - ret.push(match[0], ); - i += match[0].length; - } - ret.push(str.slice(i)); - - return ret; + // Keep this in sync with the same helper in Navigation.ts + // We use lookahead/lookbehind to indicate where the string should + // be split without consuming a character. + // (?<=[^A-Z])(?=[A-Z]) -- regular camel cased text + // (?<=[A-Z])(?=[A-Z][a-z]) -- acronym + // (?<=[_-])(?=[^_-]) -- snake + const parts = str.split(/(?<=[^A-Z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[_-])(?=[^_-])/); + return parts.flatMap(p => [p, ]).slice(0, -1); } export function join(joiner: JSX.Children, list: readonly T[], cb: (x: T) => JSX.Children) { diff --git a/src/lib/serialization/deserializer.ts b/src/lib/serialization/deserializer.ts index 4bb458f06..141c3a29d 100644 --- a/src/lib/serialization/deserializer.ts +++ b/src/lib/serialization/deserializer.ts @@ -3,6 +3,7 @@ import { ConditionalType, DeclarationReflection, DocumentReflection, + type FileId, type FileRegistry, IndexedAccessType, InferredType, @@ -19,6 +20,7 @@ import { ReferenceReflection, ReferenceType, Reflection, + type ReflectionId, ReflectionKind, ReflectionType, type ReflectionVariant, @@ -132,7 +134,7 @@ export class Deserializer { return new IntrinsicType(obj.name); }, literal(obj) { - if (obj.value && typeof obj.value === "object") { + if (typeof obj.value === "object" && obj.value != null) { return new LiteralType( BigInt( `${obj.value.negative ? "-" : ""}${obj.value.value}`, @@ -166,7 +168,7 @@ export class Deserializer { }, reference(obj) { // Correct reference will be restored in fromObject - return ReferenceType.createResolvedReference(obj.name, -2, null); + return ReferenceType.createResolvedReference(obj.name, -2 as ReflectionId, null); }, reflection(obj, de) { return new ReflectionType( @@ -213,8 +215,8 @@ export class Deserializer { */ projectRoot!: NormalizedPath; - oldIdToNewId: Record = {}; - oldFileIdToNewFileId: Record = {}; + oldIdToNewId: Record = {}; + oldFileIdToNewFileId: Record = {}; project: ProjectReflection | undefined; constructor(public logger: Logger) {} @@ -350,15 +352,15 @@ export class Deserializer { return project; } - revive>( + revive>( source: NonNullable, creator: (obj: T) => U, ): U; - revive>( + revive>( source: T | undefined, creator: (obj: T) => U, ): U | undefined; - revive>( + revive>( source: T | undefined, creator: (obj: T) => U, ): U | undefined { diff --git a/src/lib/serialization/schema.ts b/src/lib/serialization/schema.ts index 8c1861770..f1fe07a74 100644 --- a/src/lib/serialization/schema.ts +++ b/src/lib/serialization/schema.ts @@ -30,8 +30,9 @@ */ import type * as M from "#models"; -import type { IfInternal, NormalizedPath } from "#utils"; +import type { IfInternal, NormalizedPath, TagString } from "#utils"; +// Keep this in sync with JSON_SCHEMA_VERSION in ProjectReflection.ts export const SCHEMA_VERSION = "2.0"; /** @@ -57,6 +58,7 @@ type _ModelToObject = T extends M.CommentDisplayPart ? CommentDisplayPart : T extends M.SourceReference ? SourceReference : T extends M.FileRegistry ? FileRegistry : + T extends M.ReflectionSymbolId ? ReflectionSymbolId : never; type Primitive = string | number | undefined | null | boolean; @@ -76,6 +78,9 @@ type S = { -readonly [K2 in K]: ToSerialized; }; +export type ReflectionId = M.ReflectionId; +export type FileId = M.FileId; + export interface ReflectionSymbolId { packageName: string; packagePath: NormalizedPath; @@ -125,7 +130,7 @@ export interface ReferenceReflection * -1 if the reference refers to a symbol that does not exist in the documentation. * Otherwise, the reflection ID. */ - target: number; + target: ReflectionId; } /** @category Reflections */ @@ -202,7 +207,7 @@ export interface ProjectReflection extends */ schemaVersion: string; symbolIdMap: - | Record + | Record | IfInternal; files: FileRegistry; } @@ -215,7 +220,7 @@ export interface ContainerReflection extends "children" | "documents" | "groups" | "categories" > { - childrenIncludingDocuments?: number[]; + childrenIncludingDocuments?: ReflectionId[]; } /** @category Reflections */ @@ -293,7 +298,7 @@ export interface ReferenceType extends "type" | "name" | "typeArguments" | "package" | "externalUrl" > { - target: number | ReflectionSymbolId; + target: ReflectionId | ReflectionSymbolId; qualifiedName?: string; refersToTypeParameter?: boolean; preferValues?: boolean; @@ -360,11 +365,11 @@ export interface ReflectionFlags extends Partial> { summary: CommentDisplayPart[]; - modifierTags?: `@${string}`[]; + modifierTags?: TagString[]; } /** @category Comments */ -export interface CommentTag extends S { +export interface CommentTag extends S { content: CommentDisplayPart[]; } @@ -385,9 +390,9 @@ export type CommentDisplayPart = */ export interface InlineTagDisplayPart { kind: "inline-tag"; - tag: `@${string}`; + tag: TagString; text: string; - target?: string | number | ReflectionSymbolId; + target?: string | ReflectionId | ReflectionSymbolId; tsLinkText?: string; } @@ -405,7 +410,7 @@ export interface RelativeLinkDisplayPart { /** * File ID, if present */ - target?: number; + target?: FileId; /** * Anchor within the target file, if present */ @@ -416,7 +421,7 @@ export interface SourceReference extends S; + entries: Record; /** File ID to reflection ID */ - reflections: Record; + reflections: Record; } diff --git a/src/lib/types/ts-internal/index.d.ts b/src/lib/types/ts-internal/index.d.ts index 9f10898d2..f9be4f771 100644 --- a/src/lib/types/ts-internal/index.d.ts +++ b/src/lib/types/ts-internal/index.d.ts @@ -23,22 +23,8 @@ declare module "typescript" { // https://github.com/microsoft/TypeScript/blob/v5.0.2/src/compiler/utilities.ts#L7432 export function getCheckFlags(symbol: ts.Symbol): CheckFlags; - // https://github.com/microsoft/TypeScript/blob/v5.0.2/src/compiler/utilities.ts#L4171 - export function getJSDocCommentsAndTags( - hostNode: Node, - noCache?: boolean, - ): readonly (JSDoc | JSDocTag)[]; - - export function getInterfaceBaseTypeNodes( - node: InterfaceDeclaration, - ): NodeArray | undefined; - export function getAllSuperTypeNodes(node: ts.Node): readonly ts.TypeNode[]; - export interface Signature { - thisParameter?: ts.Symbol; - } - // https://github.com/microsoft/TypeScript/blob/e213b2af3430bdc9cf5fbc76a8634d832e7aaaaa/src/compiler/types.ts#L5298-L5299 export interface UnionType { /* @internal */ diff --git a/src/lib/utils-common/array.ts b/src/lib/utils-common/array.ts index 82e85bea9..7f30b8cbc 100644 --- a/src/lib/utils-common/array.ts +++ b/src/lib/utils-common/array.ts @@ -3,6 +3,7 @@ export const emptyArray: readonly [] = []; /** * Inserts an item into an array sorted by priority. If two items have the same priority, * the item will be inserted later will be placed later in the array. + * Higher priority is placed earlier in the array. * @param arr modified by inserting item. * @param item */ @@ -148,13 +149,9 @@ export function filterMap( } export function firstDefined( - array: readonly T[] | undefined, + array: readonly T[], callback: (element: T, index: number) => U | undefined, ): U | undefined { - if (array === undefined) { - return undefined; - } - for (let i = 0; i < array.length; i++) { const result = callback(array[i], i); if (result !== undefined) { @@ -175,17 +172,6 @@ export function aggregate(arr: T[], fn: (item: T) => number) { return arr.reduce((sum, it) => sum + fn(it), 0); } -export function aggregateWithJoiner( - arr: T[], - fn: (item: T) => number, - joiner: string, -) { - return ( - arr.reduce((sum, it) => sum + fn(it), 0) + - (arr.length - 1) * joiner.length - ); -} - export function joinArray( arr: readonly T[] | undefined, joiner: string, diff --git a/src/lib/utils-common/events.ts b/src/lib/utils-common/events.ts index 697d97d21..def5a16b9 100644 --- a/src/lib/utils-common/events.ts +++ b/src/lib/utils-common/events.ts @@ -22,6 +22,7 @@ export class EventDispatcher> { * @param event the event to listen to. * @param listener function to be called when an this event is emitted. * @param priority optional priority to insert this hook with. + * Higher priority is placed earlier in the listener array. */ on( event: K, diff --git a/src/lib/utils-common/general.ts b/src/lib/utils-common/general.ts index 1fcab7f2c..73f06bcad 100644 --- a/src/lib/utils-common/general.ts +++ b/src/lib/utils-common/general.ts @@ -45,6 +45,8 @@ export function assertNever(x: never): never { export function assert(x: unknown, message = "Assertion failed"): asserts x { if (!x) { + // eslint-disable-next-line no-debugger + debugger; throw new Error(message); } } diff --git a/src/lib/utils-common/i18n.ts b/src/lib/utils-common/i18n.ts index 3ee361204..22129a2dc 100644 --- a/src/lib/utils-common/i18n.ts +++ b/src/lib/utils-common/i18n.ts @@ -1,6 +1,7 @@ // Type only import to non-bundled file // eslint-disable-next-line no-restricted-imports import type { TranslationProxy } from "../internationalization/internationalization.js"; +import type { TagString } from "./validation.js"; let translations: Record = {}; @@ -36,7 +37,7 @@ export const i18n = new Proxy({}, { }, }) as TranslationProxy; -export function translateTagName(tag: `@${string}`): TranslatedString { +export function translateTagName(tag: TagString): TranslatedString { const tagName = tag.substring(1); if (Object.prototype.hasOwnProperty.call(translations, `tag_${tagName}`)) { return translations[`tag_${tagName}`] as TranslatedString; diff --git a/src/lib/utils-common/index.ts b/src/lib/utils-common/index.ts index ceb743edf..be18a0756 100644 --- a/src/lib/utils-common/index.ts +++ b/src/lib/utils-common/index.ts @@ -8,7 +8,6 @@ export * from "./events.js"; export * from "./general.js"; export * from "./hooks.js"; export * from "./i18n.js"; -export * from "./index.js"; export * as JSX from "./jsx.js"; export * from "./logger.js"; export * from "./map.js"; @@ -17,3 +16,4 @@ export * from "./path.js"; export * from "./set.js"; export * from "./string.js"; export * as Validation from "./validation.js"; +export type { TagString } from "./validation.js"; diff --git a/src/lib/utils-common/jsx.elements.ts b/src/lib/utils-common/jsx.elements.ts index d7c2fe489..c6d23dfaa 100644 --- a/src/lib/utils-common/jsx.elements.ts +++ b/src/lib/utils-common/jsx.elements.ts @@ -144,6 +144,8 @@ export type JsxChildren = | JsxElement | string | number + | boolean + | bigint | null | undefined | JsxChildren[]; diff --git a/src/lib/utils-common/jsx.ts b/src/lib/utils-common/jsx.ts index ef93e6c82..e6fd9a5a8 100644 --- a/src/lib/utils-common/jsx.ts +++ b/src/lib/utils-common/jsx.ts @@ -98,7 +98,7 @@ export function setRenderSettings(options: { pretty: boolean }) { } export function renderElement(element: JsxElement | null | undefined): string { - if (!element || typeof element === "boolean") { + if (!element) { return ""; } @@ -161,11 +161,11 @@ export function renderElement(element: JsxElement | null | undefined): string { function renderChildren(children: JsxChildren[]) { for (const child of children) { - if (!child) continue; + if (typeof child === "boolean") continue; if (Array.isArray(child)) { renderChildren(child); - } else if (typeof child === "string" || typeof child === "number") { + } else if (typeof child === "string" || typeof child === "number" || typeof child === "bigint") { html += escapeHtml(child.toString()); } else { html += renderElement(child); @@ -180,7 +180,7 @@ export function renderElement(element: JsxElement | null | undefined): string { * @internal */ export function renderElementToText(element: JsxElement | null | undefined) { - if (!element || typeof element === "boolean") { + if (!element) { return ""; } @@ -205,11 +205,12 @@ export function renderElementToText(element: JsxElement | null | undefined) { function renderChildren(children: JsxChildren[]) { for (const child of children) { - if (!child) continue; + if (typeof child === "boolean") continue; if (Array.isArray(child)) { renderChildren(child); - } else if (typeof child === "string" || typeof child === "number") { + } else if (typeof child === "string" || typeof child === "number" || typeof child === "bigint") { + // Turn non-breaking spaces into regular spaces html += child.toString().replaceAll("\u00A0", " "); } else { html += renderElementToText(child); diff --git a/src/lib/utils-common/map.ts b/src/lib/utils-common/map.ts index a893cdceb..034e1de18 100644 --- a/src/lib/utils-common/map.ts +++ b/src/lib/utils-common/map.ts @@ -49,7 +49,7 @@ export class StableKeyMap { } forEach( - callbackfn: (value: V, key: K, map: Map) => void, + callbackfn: (value: V, key: K, map: StableKeyMap) => void, thisArg?: any, ): void { for (const [k, v] of this.entries()) { diff --git a/src/lib/utils-common/path.ts b/src/lib/utils-common/path.ts index 507846a51..feec89110 100644 --- a/src/lib/utils-common/path.ts +++ b/src/lib/utils-common/path.ts @@ -1,5 +1,9 @@ import { assert } from "./general.js"; +// Type only import is permitted +// eslint-disable-next-line no-restricted-imports +import type { Application } from "../application.js"; + /** * Represents a normalized path with path separators being `/` * On Windows, drives are represented like `C:/Users` for consistency @@ -15,6 +19,12 @@ export type NormalizedPath = "" | "/" | string & { readonly __normPath: unique s */ export type NormalizedPathOrModule = NormalizedPath | string & { readonly __normPathOrModule: unique symbol }; +/** + * Represents either a {@link NormalizedPath} or a Node module name + * (e.g. `typedoc-plugin-mdn-links` or `@gerrit0/typedoc-plugin`) + */ +export type NormalizedPathOrModuleOrFunction = NormalizedPathOrModule | ((app: Application) => Promise | void); + /** * Represents a glob path configured by a user. */ diff --git a/src/lib/utils-common/validation.ts b/src/lib/utils-common/validation.ts index 8febb48d6..f6c01aa33 100644 --- a/src/lib/utils-common/validation.ts +++ b/src/lib/utils-common/validation.ts @@ -119,6 +119,8 @@ export function optional(x: T): Optional { return { [opt]: x }; } -export function isTagString(x: unknown): x is `@${string}` { - return typeof x === "string" && /^@[a-zA-Z][a-zA-Z0-9]*$/.test(x); +export type TagString = `@${string}`; + +export function isTagString(x: unknown): x is TagString { + return typeof x === "string" && /^@[a-z][a-z0-9-]*$/i.test(x); } diff --git a/src/lib/utils/ValidatingFileRegistry.ts b/src/lib/utils/ValidatingFileRegistry.ts index f16afad64..0e26dc72a 100644 --- a/src/lib/utils/ValidatingFileRegistry.ts +++ b/src/lib/utils/ValidatingFileRegistry.ts @@ -1,17 +1,36 @@ -import { FileRegistry } from "../models/FileRegistry.js"; -import { isFile } from "./fs.js"; +import { type FileId, FileRegistry } from "../models/FileRegistry.js"; import type { Deserializer, JSONOutput } from "#serialization"; import { i18n, type NormalizedPath, NormalizedPathUtils } from "#utils"; +import { existsSync } from "fs"; export class ValidatingFileRegistry extends FileRegistry { + basePath: NormalizedPath; + + constructor(basePath: NormalizedPath = "") { + super(); + this.basePath = basePath; + } + override register( sourcePath: NormalizedPath, relativePath: NormalizedPath, - ): { target: number; anchor: string | undefined } | undefined { - const absolute = NormalizedPathUtils.resolve(NormalizedPathUtils.dirname(sourcePath), relativePath); - const absoluteWithoutAnchor = absolute.replace(/#.*/, ""); - if (!isFile(absoluteWithoutAnchor)) { - return; + ): { target: FileId; anchor: string | undefined } | undefined { + let absolute = NormalizedPathUtils.resolve(NormalizedPathUtils.dirname(sourcePath), relativePath); + let absoluteWithoutAnchor = absolute.replace(/#.*/, ""); + // Note: We allow paths to directories to be registered here, but the AssetsPlugin will not + // copy them to the output path. This is so that we can link to directories and associate them + // with reflections in packages mode. + if (!existsSync(absoluteWithoutAnchor)) { + // If the relative path didn't exist normally, also check the path relative to the assetBasePath option + if (this.basePath != "") { + absolute = NormalizedPathUtils.resolve(this.basePath, relativePath); + absoluteWithoutAnchor = absolute.replace(/#.*/, ""); + if (!existsSync(absoluteWithoutAnchor)) { + return; + } + } else { + return; + } } return this.registerAbsolute(absolute); } @@ -19,9 +38,9 @@ export class ValidatingFileRegistry extends FileRegistry { override fromObject(de: Deserializer, obj: JSONOutput.FileRegistry) { for (const [key, val] of Object.entries(obj.entries)) { const absolute = NormalizedPathUtils.resolve(de.projectRoot, val); - if (!isFile(absolute)) { + if (!existsSync(absolute)) { de.logger.warn( - i18n.saved_relative_path_0_resolved_from_1_is_not_a_file( + i18n.saved_relative_path_0_resolved_from_1_does_not_exist( val, de.projectRoot, ), @@ -29,7 +48,7 @@ export class ValidatingFileRegistry extends FileRegistry { continue; } - de.oldFileIdToNewFileId[+key] = this.registerAbsolute(absolute).target; + de.oldFileIdToNewFileId[+key as FileId] = this.registerAbsolute(absolute).target; } de.defer((project) => { @@ -39,7 +58,7 @@ export class ValidatingFileRegistry extends FileRegistry { ); if (refl) { this.mediaToReflection.set( - de.oldFileIdToNewFileId[+media]!, + de.oldFileIdToNewFileId[+media as FileId]!, refl.id, ); } else { diff --git a/src/lib/utils/declaration-maps.ts b/src/lib/utils/declaration-maps.ts new file mode 100644 index 000000000..a6183d6f7 --- /dev/null +++ b/src/lib/utils/declaration-maps.ts @@ -0,0 +1,71 @@ +import type ts from "typescript"; +import { existsSync } from "fs"; +import { readFile } from "./fs.js"; +import { Validation } from "#utils"; +import { join, relative, resolve } from "path"; +import { getCommonDirectory, normalizePath } from "./paths.js"; + +const declarationMapCache = new Map(); + +export function resolveDeclarationMaps(file: string): string { + if (!/\.d\.[cm]?ts$/.test(file)) return file; + if (declarationMapCache.has(file)) return declarationMapCache.get(file)!; + + const mapFile = file + ".map"; + if (!existsSync(mapFile)) return file; + + let sourceMap: unknown; + try { + sourceMap = JSON.parse(readFile(mapFile)) as unknown; + } catch { + return file; + } + + if ( + Validation.validate( + { + file: String, + sourceRoot: Validation.optional(String), + sources: [Array, String], + }, + sourceMap, + ) + ) { + // There's a pretty large assumption in here that we only have + // 1 source file per js file. This is a pretty standard typescript approach, + // but people might do interesting things with transpilation that could break this. + let source = sourceMap.sources[0]; + + // If we have a sourceRoot, trim any leading slash from the source, and join them + // Similar to how it's done at https://github.com/mozilla/source-map/blob/58819f09018d56ef84dc41ba9c93f554e0645169/lib/util.js#L412 + if (sourceMap.sourceRoot !== undefined) { + source = source.replace(/^\//, ""); + source = join(sourceMap.sourceRoot, source); + } + + const result = resolve(mapFile, "..", source); + declarationMapCache.set(file, result); + return result; + } + + return file; +} + +// See also: inferEntryPoints in entry-point.ts +export function addInferredDeclarationMapPaths( + opts: ts.CompilerOptions, + files: readonly string[], +) { + const rootDir = opts.rootDir || getCommonDirectory(files); + const declDir = opts.declarationDir || opts.outDir || rootDir; + + for (const file of files) { + const mapFile = normalizePath( + resolve(declDir, relative(rootDir, file)).replace( + /\.([cm]?[tj]s)x?$/, + ".d.$1", + ), + ); + declarationMapCache.set(mapFile, file); + } +} diff --git a/src/lib/utils/entry-point.ts b/src/lib/utils/entry-point.ts index badb348fe..35f90d8b0 100644 --- a/src/lib/utils/entry-point.ts +++ b/src/lib/utils/entry-point.ts @@ -6,6 +6,7 @@ import { deriveRootDir, getCommonDirectory, MinimatchSet, nicePath, normalizePat import type { Options } from "./options/index.js"; import { discoverPackageJson, glob, inferPackageEntryPointPaths, isDir } from "./fs.js"; import { assertNever, type GlobString, i18n, type Logger, type NormalizedPath } from "#utils"; +import { addInferredDeclarationMapPaths, resolveDeclarationMaps } from "./declaration-maps.js"; /** * Defines how entry points are interpreted. @@ -65,7 +66,7 @@ export function inferEntryPoints(logger: Logger, options: Options, programs?: ts options, ); - // See also: addInferredDeclarationMapPaths in ReflectionSymbolId + // See also: addInferredDeclarationMapPaths in symbol-id factory const jsToTsSource = new Map(); for (const program of programs) { const opts = program.getCompilerOptions(); @@ -86,7 +87,7 @@ export function inferEntryPoints(logger: Logger, options: Options, programs?: ts for (const [name, path] of pathEntries) { // Strip leading ./ from the display name const displayName = name.replace(/^\.\/?/, ""); - const targetPath = jsToTsSource.get(path) || path; + const targetPath = jsToTsSource.get(path) || resolveDeclarationMaps(path) || path; const program = programs.find((p) => p.getSourceFile(targetPath)); if (program) { @@ -107,6 +108,10 @@ export function inferEntryPoints(logger: Logger, options: Options, programs?: ts return []; } + logger.verbose( + `Inferred entry points to be:\n\t${entryPoints.map(e => nicePath(e.sourceFile.fileName)).join("\n\t")}`, + ); + return entryPoints; } @@ -191,7 +196,7 @@ export function getDocumentEntryPoints( options, supportedFileRegex, ); - const baseDir = options.getValue("basePath") || getCommonDirectory(expanded); + const baseDir = options.getValue("displayBasePath") || options.getValue("basePath") || getCommonDirectory(expanded); return expanded.map((path) => { return { displayName: relative(baseDir, path).replace(/\.[^.]+$/, ""), @@ -288,7 +293,8 @@ function getEntryPointsForPaths( options: Options, programs = getEntryPrograms(inputFiles, logger, options), ): DocumentationEntryPoint[] { - const baseDir = options.getValue("basePath") || getCommonDirectory(inputFiles); + const baseDir = options.getValue("displayBasePath") || options.getValue("basePath") || + getCommonDirectory(inputFiles); const entryPoints: DocumentationEntryPoint[] = []; let expandSuggestion = true; @@ -339,7 +345,7 @@ export function getExpandedEntryPointsForPaths( options: Options, programs = getEntryPrograms(inputFiles, logger, options), ): DocumentationEntryPoint[] { - const compilerOptions = options.getCompilerOptions(); + const compilerOptions = options.getCompilerOptions(logger); const supportedFileRegex = compilerOptions.allowJs || compilerOptions.checkJs ? /\.([cm][tj]s|[tj]sx?)$/ : /\.([cm]ts|tsx?)$/; @@ -372,9 +378,6 @@ function expandGlobs(globs: GlobString[], exclude: GlobString[], logger: Logger) logger.warn( i18n.glob_0_did_not_match_any_files(entry), ); - if (entry.includes("\\") && !entry.includes("/")) { - logger.info(i18n.glob_should_use_posix_slash()); - } } else if (filtered.length === 0) { logger.warn( i18n.entry_point_0_did_not_match_any_files_after_exclude( @@ -408,14 +411,19 @@ function getEntryPrograms( const rootProgram = noTsConfigFound ? ts.createProgram({ rootNames: inputFiles, - options: options.getCompilerOptions(), + options: options.getCompilerOptions(logger), }) : ts.createProgram({ rootNames: options.getFileNames(), - options: options.getCompilerOptions(), + options: options.getCompilerOptions(logger), projectReferences: options.getProjectReferences(), }); + addInferredDeclarationMapPaths( + options.getCompilerOptions(logger), + options.getFileNames(), + ); + const programs = [rootProgram]; // This might be a solution style tsconfig, in which case we need to add a program for each // reference so that the converter can look through each of these. @@ -431,11 +439,17 @@ function getEntryPrograms( ts.createProgram({ options: options.fixCompilerOptions( ref.commandLine.options, + logger, ), rootNames: ref.commandLine.fileNames, projectReferences: ref.commandLine.projectReferences, }), ); + + addInferredDeclarationMapPaths( + ref.commandLine.options, + ref.commandLine.fileNames, + ); } } diff --git a/src/lib/utils/highlighter.tsx b/src/lib/utils/highlighter.tsx index 0041a2ccc..e539dafb4 100644 --- a/src/lib/utils/highlighter.tsx +++ b/src/lib/utils/highlighter.tsx @@ -2,7 +2,8 @@ import * as shiki from "@gerrit0/mini-shiki"; import { JSX, unique } from "#utils"; import assert from "assert"; -const aliases = new Map(); +const tsAliases: [string, string][] = [["mts", "typescript"], ["cts", "typescript"]]; +const aliases = new Map(tsAliases); for (const lang of shiki.bundledLanguagesInfo) { for (const alias of lang.aliases || []) { aliases.set(alias, lang.id); @@ -161,8 +162,12 @@ export async function loadHighlighter( highlighter = new ShikiHighlighter(hl, lightTheme, darkTheme); } +function isPlainLanguage(lang: string) { + return ignoredLanguages?.includes(lang) || plaintextLanguages.includes(lang); +} + export function isSupportedLanguage(lang: string) { - return ignoredLanguages?.includes(lang) || getSupportedLanguages().includes(lang); + return isPlainLanguage(lang) || supportedLanguages.includes(lang); } export function getSupportedLanguages(): string[] { @@ -174,9 +179,7 @@ export function getSupportedThemes(): string[] { } export function isLoadedLanguage(lang: string): boolean { - return ( - plaintextLanguages.includes(lang) || ignoredLanguages?.includes(lang) || highlighter?.supports(lang) || false - ); + return isPlainLanguage(lang) || highlighter?.supports(lang) || false; } export function highlight(code: string, lang: string): string { diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 605a8b406..a48c4f739 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -44,6 +44,7 @@ export type { SortStrategy } from "./sort.js"; export * from "./entry-point.js"; +export * from "./declaration-maps.js"; export * from "./highlighter.js"; export * from "./html.js"; export * from "./tsconfig.js"; diff --git a/src/lib/utils/options/declaration.ts b/src/lib/utils/options/declaration.ts index 6a8c476b4..ef5613ca8 100644 --- a/src/lib/utils/options/declaration.ts +++ b/src/lib/utils/options/declaration.ts @@ -10,10 +10,13 @@ import { type NeverIfInternal, type NormalizedPath, type NormalizedPathOrModule, + type NormalizedPathOrModuleOrFunction, + type TagString, type TranslatedString, } from "#utils"; import type { TranslationProxy } from "../../internationalization/internationalization.js"; import { createGlobString, normalizePath } from "../paths.js"; +import type { Application } from "../../application.js"; /** @enum */ export const EmitStrategy = { @@ -122,7 +125,8 @@ export type TypeDocOptions = { TypeDocOptionMap[K] extends ManuallyValidatedOption< infer ManuallyValidated > ? ManuallyValidated : - TypeDocOptionMap[K] extends NormalizedPath[] | NormalizedPathOrModule[] | GlobString[] ? string[] : + TypeDocOptionMap[K] extends + NormalizedPath[] | NormalizedPathOrModule[] | NormalizedPathOrModuleOrFunction[] | GlobString[] ? string[] : TypeDocOptionMap[K] extends NormalizedPath ? string : TypeDocOptionMap[K] extends | string @@ -150,6 +154,8 @@ export type TypeDocOptionValues = { | string | string[] | GlobString[] + | NormalizedPathOrModule[] + | NormalizedPathOrModuleOrFunction[] | number | boolean | Record ? TypeDocOptionMap[K] : @@ -183,7 +189,7 @@ export interface TypeDocOptionMap { options: NormalizedPath; tsconfig: NormalizedPath; compilerOptions: unknown; - plugin: NormalizedPathOrModule[]; + plugin: NormalizedPathOrModuleOrFunction[]; lang: string; locales: ManuallyValidatedOption>>; packageOptions: ManuallyValidatedOption< @@ -201,6 +207,7 @@ export interface TypeDocOptionMap { excludeNotDocumented: boolean; excludeNotDocumentedKinds: ReflectionKind.KindString[]; excludeInternal: boolean; + excludePrivateClassFields: boolean; excludePrivate: boolean; excludeProtected: boolean; excludeReferences: boolean; @@ -216,6 +223,7 @@ export interface TypeDocOptionMap { gitRevision: string; gitRemote: string; readme: string; + basePath: NormalizedPath; // Output outputs: ManuallyValidatedOption>; @@ -255,7 +263,7 @@ export interface TypeDocOptionMap { * strictly typed here. */ markdownItLoader: ManuallyValidatedOption<(parser: any) => void>; - basePath: NormalizedPath; + displayBasePath: NormalizedPath; cname: string; favicon: NormalizedPath; githubPages: boolean; @@ -292,7 +300,7 @@ export interface TypeDocOptionMap { private?: boolean; inherited?: boolean; external?: boolean; - [tag: `@${string}`]: boolean; + [tag: TagString]: boolean; }>; searchCategoryBoosts: ManuallyValidatedOption>; searchGroupBoosts: ManuallyValidatedOption>; @@ -304,15 +312,16 @@ export interface TypeDocOptionMap { preserveLinkText: boolean; jsDocCompatibility: JsDocCompatibility; suppressCommentWarningsInDeclarationFiles: boolean; - blockTags: `@${string}`[]; - inlineTags: `@${string}`[]; - modifierTags: `@${string}`[]; - excludeTags: `@${string}`[]; - notRenderedTags: `@${string}`[]; + blockTags: TagString[]; + inlineTags: TagString[]; + modifierTags: TagString[]; + excludeTags: TagString[]; + notRenderedTags: TagString[]; externalSymbolLinkMappings: ManuallyValidatedOption< Record> >; - cascadedModifierTags: `@${string}`[]; + cascadedModifierTags: TagString[]; + preservedTypeAnnotationTags: TagString[]; // Organization categorizeByGroup: boolean; @@ -404,7 +413,9 @@ export type KeyToDeclaration = TypeDocOptionMa TypeDocOptionMap[K] extends string | NormalizedPath ? StringDeclarationOption : TypeDocOptionMap[K] extends number ? NumberDeclarationOption : TypeDocOptionMap[K] extends GlobString[] ? GlobArrayDeclarationOption : - TypeDocOptionMap[K] extends string[] | NormalizedPath[] | NormalizedPathOrModule[] ? ArrayDeclarationOption : + TypeDocOptionMap[K] extends + string[] | NormalizedPath[] | NormalizedPathOrModule[] | NormalizedPathOrModuleOrFunction[] ? + ArrayDeclarationOption : unknown extends TypeDocOptionMap[K] ? MixedDeclarationOption | ObjectDeclarationOption : TypeDocOptionMap[K] extends ManuallyValidatedOption ? | (MixedDeclarationOption & { @@ -452,8 +463,14 @@ export enum ParameterType { PathArray, /** * Resolved according to the config directory if it starts with `.` + * @deprecated since 0.28.8, will be removed in 0.29 */ ModuleArray, + /** + * Resolved according to the config directory if it starts with `.` + * @internal - only intended for use with the plugin option + */ + PluginArray, /** * Relative to the config directory. */ @@ -567,7 +584,9 @@ export interface ArrayDeclarationOption extends DeclarationOptionBase { type: | ParameterType.Array | ParameterType.PathArray - | ParameterType.ModuleArray; + // eslint-disable-next-line @typescript-eslint/no-deprecated + | ParameterType.ModuleArray + | ParameterType.PluginArray; /** * If not specified defaults to an empty array. @@ -673,7 +692,9 @@ export interface ParameterTypeToOptionTypeMap { [ParameterType.Object]: unknown; [ParameterType.Array]: string[]; [ParameterType.PathArray]: NormalizedPath[]; + // eslint-disable-next-line @typescript-eslint/no-deprecated [ParameterType.ModuleArray]: NormalizedPathOrModule[]; + [ParameterType.PluginArray]: Array void | Promise)>; [ParameterType.GlobArray]: GlobString[]; [ParameterType.Flags]: Record; @@ -685,6 +706,29 @@ export type DeclarationOptionToOptionType = T exten T extends FlagsDeclarationOption ? U : ParameterTypeToOptionTypeMap[Exclude]; +function toStringArray(value: unknown, option: DeclarationOption): string[] { + if (Array.isArray(value) && value.every(v => typeof v === "string")) { + return value; + } else if (typeof value === "string") { + return [value]; + } + + throw new Error(i18n.option_0_must_be_an_array_of_string(option.name)); +} + +function toStringOrFunctionArray( + value: unknown, + option: DeclarationOption, +): Array void | Promise)> { + if (Array.isArray(value) && value.every(v => typeof v === "string" || typeof v === "function")) { + return value; + } else if (typeof value === "string") { + return [value]; + } + + throw new Error(i18n.option_0_must_be_an_array_of_string_or_functions(option.name)); +} + const converters: { [K in ParameterType]: ( value: unknown, @@ -737,40 +781,46 @@ const converters: { return !!value; }, [ParameterType.Array](value, option) { - let strArrValue: string[] = []; - if (Array.isArray(value)) { - strArrValue = value.map(String); - } else if (typeof value === "string") { - strArrValue = [value]; - } + const strArrValue = toStringArray(value, option); option.validate?.(strArrValue); return strArrValue; }, [ParameterType.PathArray](value, option, configPath) { - let strArrValue: string[] = []; - if (Array.isArray(value)) { - strArrValue = value.map(String); - } else if (typeof value === "string") { - strArrValue = [value]; - } + const strArrValue = toStringArray(value, option); const normalized = strArrValue.map((path) => normalizePath(resolve(configPath, path))); option.validate?.(normalized); return normalized; }, + // eslint-disable-next-line @typescript-eslint/no-deprecated [ParameterType.ModuleArray](value, option, configPath) { - let strArrValue: string[] = []; - if (Array.isArray(value)) { - strArrValue = value.map(String); - } else if (typeof value === "string") { - strArrValue = [value]; - } + const strArrValue = toStringArray(value, option); const resolved = resolveModulePaths(strArrValue, configPath); option.validate?.(resolved); return resolved; }, + [ParameterType.PluginArray](value, option, configPath) { + const arrayValue = toStringOrFunctionArray(value, option); + const resolved = arrayValue.map(plugin => + typeof plugin === "function" ? plugin : resolveModulePath(plugin, configPath) + ); + return resolved; + }, [ParameterType.GlobArray](value, option, configPath) { - const toGlobString = (v: unknown) => createGlobString(configPath, String(v)); - const globs = Array.isArray(value) ? value.map(toGlobString) : [toGlobString(value)]; + const toGlobString = (v: unknown) => { + const s = String(v); + + // If the string tries to escape a character which isn't a special + // glob character, the user probably provided a Windows style path + // by accident due to shell completion, tell them to either remove + // the useless escape or switch to Unix path separators. + if (/\\[^?*()[\]\\{}]/.test(s)) { + throw new Error(i18n.glob_0_should_use_posix_slash(s)); + } + + return createGlobString(configPath, s); + }; + const strArrValue = toStringArray(value, option); + const globs = strArrValue.map(toGlobString); option.validate?.(globs); return globs; }, @@ -928,12 +978,19 @@ const defaultGetters: { option.defaultValue?.map((value) => normalizePath(resolve(process.cwd(), value))) ?? [] ); }, + // eslint-disable-next-line @typescript-eslint/no-deprecated [ParameterType.ModuleArray](option) { if (option.defaultValue) { return resolveModulePaths(option.defaultValue, process.cwd()); } return []; }, + [ParameterType.PluginArray](option) { + if (option.defaultValue) { + return resolveModulePaths(option.defaultValue, process.cwd()); + } + return []; + }, [ParameterType.GlobArray](option) { return (option.defaultValue ?? []).map(g => createGlobString(normalizePath(process.cwd()), g)); }, @@ -951,12 +1008,14 @@ export function getDefaultValue(option: DeclarationOption) { } function resolveModulePaths(modules: readonly string[], configPath: string): NormalizedPathOrModule[] { - return modules.map((path) => { - if (path.startsWith(".")) { - return normalizePath(resolve(configPath, path)); - } - return normalizePath(path); - }); + return modules.map(path => resolveModulePath(path, configPath)); +} + +function resolveModulePath(path: string, configPath: string): NormalizedPathOrModule { + if (path.startsWith(".")) { + return normalizePath(resolve(configPath, path)); + } + return normalizePath(path); } function isTsNumericEnum(map: Record) { diff --git a/src/lib/utils/options/defaults.ts b/src/lib/utils/options/defaults.ts index 20d6d1472..3751c3183 100644 --- a/src/lib/utils/options/defaults.ts +++ b/src/lib/utils/options/defaults.ts @@ -4,7 +4,7 @@ */ import type { BundledLanguage } from "@gerrit0/mini-shiki"; import * as TagDefaults from "./tsdoc-defaults.js"; -import type { EnumKeys } from "#utils"; +import type { EnumKeys, TagString } from "#utils"; import type { ReflectionKind } from "../../models/index.js"; export const excludeNotDocumentedKinds: readonly EnumKeys< @@ -31,7 +31,7 @@ export const excludeNotDocumentedKinds: readonly EnumKeys< "Reference", ]; -export const excludeTags: readonly `@${string}`[] = [ +export const excludeTags: readonly TagString[] = [ "@override", "@virtual", "@privateRemarks", @@ -41,17 +41,19 @@ export const excludeTags: readonly `@${string}`[] = [ "@inlineType", ]; -export const blockTags: readonly `@${string}`[] = TagDefaults.blockTags; -export const inlineTags: readonly `@${string}`[] = TagDefaults.inlineTags; -export const modifierTags: readonly `@${string}`[] = TagDefaults.modifierTags; +export const blockTags: readonly TagString[] = TagDefaults.blockTags; +export const inlineTags: readonly TagString[] = TagDefaults.inlineTags; +export const modifierTags: readonly TagString[] = TagDefaults.modifierTags; -export const cascadedModifierTags: readonly `@${string}`[] = [ +export const cascadedModifierTags: readonly TagString[] = [ "@alpha", "@beta", "@experimental", ]; -export const notRenderedTags: readonly `@${string}`[] = [ +export const preservedTypeAnnotationTags: readonly TagString[] = []; + +export const notRenderedTags: readonly TagString[] = [ "@showCategories", "@showGroups", "@hideCategories", diff --git a/src/lib/utils/options/options.ts b/src/lib/utils/options/options.ts index 49e0798d8..797cccb01 100644 --- a/src/lib/utils/options/options.ts +++ b/src/lib/utils/options/options.ts @@ -1,4 +1,4 @@ -import type ts from "typescript"; +import ts from "typescript"; import { resolve } from "path"; import { ParameterType } from "./declaration.js"; import type { OutputSpecification } from "../index.js"; @@ -361,19 +361,34 @@ export class Options { /** * Gets the set compiler options. */ - getCompilerOptions(): ts.CompilerOptions { - return this.fixCompilerOptions(this._compilerOptions); + getCompilerOptions(logger: Logger): ts.CompilerOptions { + return this.fixCompilerOptions(this._compilerOptions, logger); } /** @internal */ fixCompilerOptions( options: Readonly, + logger: Logger, ): ts.CompilerOptions { const overrides = this.getValue("compilerOptions"); const result = { ...options }; if (overrides) { - Object.assign(result, overrides); + const tsOptions = ts.convertCompilerOptionsFromJson(overrides, ".", "typedoc-overrides.json"); + + if (tsOptions.errors.length) { + for (const error of tsOptions.errors) { + logger.error( + i18n.failed_to_apply_compilerOptions_overrides_0( + ts.flattenDiagnosticMessageText(error.messageText, "\n"), + ), + ); + } + } else { + for (const key in overrides) { + result[key] = tsOptions.options[key]; + } + } } if (this.getValue("emit") !== "both") { diff --git a/src/lib/utils/options/readers/arguments.ts b/src/lib/utils/options/readers/arguments.ts index bc8ceb2e5..2e8dbb3c6 100644 --- a/src/lib/utils/options/readers/arguments.ts +++ b/src/lib/utils/options/readers/arguments.ts @@ -6,7 +6,9 @@ import { i18n, type Logger, type TranslatedString } from "#utils"; const ARRAY_OPTION_TYPES = new Set([ ParameterType.Array, ParameterType.PathArray, + // eslint-disable-next-line @typescript-eslint/no-deprecated ParameterType.ModuleArray, + ParameterType.PluginArray, ParameterType.GlobArray, ]); @@ -18,12 +20,18 @@ export class ArgumentsReader implements OptionsReader { readonly order: number; readonly supportsPackages = false; private args: string[]; + private skipErrorReporting = false; constructor(priority: number, args = process.argv.slice(2)) { this.order = priority; this.args = args; } + ignoreErrors() { + this.skipErrorReporting = true; + return this; + } + read(container: Options, logger: Logger): void { // Make container's type more lax, we do the appropriate checks manually. const options = container as Options & { @@ -38,7 +46,9 @@ export class ArgumentsReader implements OptionsReader { options.setValue(name, value); } catch (err) { ok(err instanceof Error); - logger.error(err.message as TranslatedString); + if (!this.skipErrorReporting) { + logger.error(err.message as TranslatedString); + } } }; @@ -50,11 +60,13 @@ export class ArgumentsReader implements OptionsReader { if (decl) { if (decl.configFileOnly) { - logger.error( - i18n.option_0_can_only_be_specified_by_config_file( - decl.name, - ), - ); + if (!this.skipErrorReporting) { + logger.error( + i18n.option_0_can_only_be_specified_by_config_file( + decl.name, + ), + ); + } continue; } @@ -69,7 +81,7 @@ export class ArgumentsReader implements OptionsReader { decl.type === ParameterType.Boolean || decl.type === ParameterType.Flags ) { - const value = String(this.args[index]).toLowerCase(); + const value = String(this.args.at(index)).toLowerCase(); if (value === "true" || value === "false") { trySet(decl.name, value === "true"); @@ -81,11 +93,13 @@ export class ArgumentsReader implements OptionsReader { } else { if (index === this.args.length) { // Only boolean values have optional values. - logger.warn( - i18n.option_0_expected_a_value_but_none_provided( - decl.name, - ), - ); + if (!this.skipErrorReporting) { + logger.warn( + i18n.option_0_expected_a_value_but_none_provided( + decl.name, + ), + ); + } } trySet(decl.name, this.args[index]); } @@ -100,7 +114,7 @@ export class ArgumentsReader implements OptionsReader { if (decl && decl.type === ParameterType.Flags) { const flagName = name.split(".", 2)[1]; - const value = String(this.args[index]).toLowerCase(); + const value = String(this.args.at(index)).toLowerCase(); if (value === "true" || value === "false") { trySet(decl.name, { [flagName]: value === "true" }); @@ -115,12 +129,14 @@ export class ArgumentsReader implements OptionsReader { } } - logger.error( - i18n.unknown_option_0_may_have_meant_1( - name, - options.getSimilarOptions(name).join("\n\t"), - ), - ); + if (!this.skipErrorReporting) { + logger.error( + i18n.unknown_option_0_may_have_meant_1( + name, + options.getSimilarOptions(name).join("\n\t"), + ), + ); + } index++; } } diff --git a/src/lib/utils/options/readers/tsconfig.ts b/src/lib/utils/options/readers/tsconfig.ts index 45add3be1..42d1a439f 100644 --- a/src/lib/utils/options/readers/tsconfig.ts +++ b/src/lib/utils/options/readers/tsconfig.ts @@ -5,14 +5,14 @@ import ts from "typescript"; import type { Options, OptionsReader } from "../options.js"; import { isFile } from "../../fs.js"; import { ok } from "assert"; -import { i18n, type Logger, type TranslatedString, unique, Validation } from "#utils"; +import { i18n, type Logger, type TagString, type TranslatedString, unique, Validation } from "#utils"; import { nicePath, normalizePath } from "../../paths.js"; import { createRequire } from "module"; import { tsdocBlockTags, tsdocInlineTags, tsdocModifierTags } from "../tsdoc-defaults.js"; import { findTsConfigFile, getTypeDocOptionsFromTsConfig, readTsConfig } from "../../tsconfig.js"; import { diagnostics } from "../../loggers.js"; -function isSupportForTags(obj: unknown): obj is Record<`@${string}`, boolean> { +function isSupportForTags(obj: unknown): obj is Record { return ( Validation.validate({}, obj) && Object.entries(obj).every(([key, val]) => { @@ -148,15 +148,15 @@ export class TSConfigReader implements OptionsReader { const config = this.readTsDoc(logger, tsdoc); if (!config) return; - const supported = (tag: { tagName: `@${string}` }) => { + const supported = (tag: { tagName: TagString }) => { return config.supportForTags ? !!config.supportForTags[tag.tagName] : true; }; - const blockTags: `@${string}`[] = []; - const inlineTags: `@${string}`[] = []; - const modifierTags: `@${string}`[] = []; + const blockTags: TagString[] = []; + const inlineTags: TagString[] = []; + const modifierTags: TagString[] = []; if (!config.noStandardTags) { blockTags.push(...tsdocBlockTags); diff --git a/src/lib/utils/options/sources/typedoc.ts b/src/lib/utils/options/sources/typedoc.ts index c87d7c4e5..f572bfd33 100644 --- a/src/lib/utils/options/sources/typedoc.ts +++ b/src/lib/utils/options/sources/typedoc.ts @@ -20,9 +20,7 @@ function makeTagArrayValidator(name: keyof TypeDocOptionMap) { // For convenience, added in the same order as they are documented on the website. export function addTypeDocOptions(options: Pick) { - /////////////////////////// - // Configuration Options // - /////////////////////////// + // MARK: Configuration Options options.addDeclaration({ type: ParameterType.Path, @@ -96,9 +94,7 @@ export function addTypeDocOptions(options: Pick) { }, }); - /////////////////////////// - ////// Input Options ////// - /////////////////////////// + // MARK: Input Options options.addDeclaration({ name: "entryPoints", @@ -193,6 +189,12 @@ export function addTypeDocOptions(options: Pick) { type: ParameterType.Boolean, defaultValue: true, }); + options.addDeclaration({ + name: "excludePrivateClassFields", + help: () => i18n.help_excludePrivateClassFields(), + type: ParameterType.Boolean, + defaultValue: true, + }); options.addDeclaration({ name: "excludeProtected", help: () => i18n.help_excludeProtected(), @@ -232,10 +234,18 @@ export function addTypeDocOptions(options: Pick) { } }, }); + options.addDeclaration({ + name: "readme", + help: () => i18n.help_readme(), + type: ParameterType.Path, + }); + options.addDeclaration({ + name: "basePath", + help: () => i18n.help_basePath(), + type: ParameterType.Path, + }); - /////////////////////////// - ///// Output Options ////// - /////////////////////////// + // MARK: Output Options options.addDeclaration({ name: "outputs", @@ -454,13 +464,8 @@ export function addTypeDocOptions(options: Pick) { type: ParameterType.Boolean, }); options.addDeclaration({ - name: "basePath", - help: () => i18n.help_basePath(), - type: ParameterType.Path, - }); - options.addDeclaration({ - name: "readme", - help: () => i18n.help_readme(), + name: "displayBasePath", + help: () => i18n.help_displayBasePath(), type: ParameterType.Path, }); options.addDeclaration({ @@ -650,7 +655,7 @@ export function addTypeDocOptions(options: Pick) { }, validate(value) { const knownKeys = ["protected", "private", "inherited", "external"]; - if (!value || typeof value !== "object") { + if (typeof value !== "object" || !value) { throw new Error( i18n.option_0_must_be_an_object("visibilityFilters"), ); @@ -720,9 +725,7 @@ export function addTypeDocOptions(options: Pick) { type: ParameterType.Boolean, }); - /////////////////////////// - ///// Comment Options ///// - /////////////////////////// + // MARK: Comment Options options.addDeclaration({ name: "jsDocCompatibility", @@ -806,10 +809,15 @@ export function addTypeDocOptions(options: Pick) { defaultValue: OptionDefaults.cascadedModifierTags, validate: makeTagArrayValidator("cascadedModifierTags"), }); + options.addDeclaration({ + name: "preservedTypeAnnotationTags", + help: () => i18n.help_preservedTypeAnnotationTags(), + type: ParameterType.Array, + defaultValue: OptionDefaults.preservedTypeAnnotationTags, + validate: makeTagArrayValidator("preservedTypeAnnotationTags"), + }); - /////////////////////////// - // Organization Options /// - /////////////////////////// + // MARK: Organization Options options.addDeclaration({ name: "categorizeByGroup", @@ -845,10 +853,7 @@ export function addTypeDocOptions(options: Pick) { type: ParameterType.Array, defaultValue: OptionDefaults.sort, validate(value) { - const invalid = new Set(value); - for (const v of SORT_STRATEGIES) { - invalid.delete(v); - } + const invalid = setDifference(value, SORT_STRATEGIES); if (invalid.size !== 0) { throw new Error( @@ -887,9 +892,7 @@ export function addTypeDocOptions(options: Pick) { }, }); - /////////////////////////// - ///// General Options ///// - /////////////////////////// + // MARK: General Options options.addDeclaration({ name: "watch", @@ -926,7 +929,7 @@ export function addTypeDocOptions(options: Pick) { options.addDeclaration({ name: "plugin", help: () => i18n.help_plugin(), - type: ParameterType.ModuleArray, + type: ParameterType.PluginArray, }); options.addDeclaration({ name: "logLevel", diff --git a/src/lib/utils/options/tsdoc-defaults.ts b/src/lib/utils/options/tsdoc-defaults.ts index 39da35ef6..1aeabc65a 100644 --- a/src/lib/utils/options/tsdoc-defaults.ts +++ b/src/lib/utils/options/tsdoc-defaults.ts @@ -37,7 +37,9 @@ export const blockTags = [ "@return", "@satisfies", "@since", + "@sortStrategy", "@template", // Alias for @typeParam + "@this", "@type", "@typedef", "@summary", diff --git a/src/lib/utils/plugins.ts b/src/lib/utils/plugins.ts index 99f726639..591a9cceb 100644 --- a/src/lib/utils/plugins.ts +++ b/src/lib/utils/plugins.ts @@ -3,35 +3,42 @@ import { pathToFileURL } from "url"; import type { Application } from "../application.js"; import { nicePath } from "./paths.js"; -import { i18n, type TranslatedString } from "#utils"; +import { i18n, type NormalizedPathOrModuleOrFunction, type TranslatedString } from "#utils"; export async function loadPlugins( app: Application, - plugins: readonly string[], + plugins: readonly NormalizedPathOrModuleOrFunction[], ) { for (const plugin of plugins) { const pluginDisplay = getPluginDisplayName(plugin); try { - let instance: any; - // Try importing first to avoid warnings about requiring ESM being experimental. - // If that fails due to importing a directory, fall back to require. - try { - // On Windows, we need to ensure this path is a file path. - // Or we'll get ERR_UNSUPPORTED_ESM_URL_SCHEME - const esmPath = isAbsolute(plugin) - ? pathToFileURL(plugin).toString() - : plugin; - instance = await import(esmPath); - } catch (error: any) { - if (error.code === "ERR_UNSUPPORTED_DIR_IMPORT") { - // eslint-disable-next-line @typescript-eslint/no-require-imports - instance = require(plugin); - } else { - throw error; + let initFunction: any; + + if (typeof plugin === "function") { + initFunction = plugin; + } else { + let instance: any; + + // Try importing first to avoid warnings about requiring ESM being experimental. + // If that fails due to importing a directory, fall back to require. + try { + // On Windows, we need to ensure this path is a file path. + // Or we'll get ERR_UNSUPPORTED_ESM_URL_SCHEME + const esmPath = isAbsolute(plugin) + ? pathToFileURL(plugin).toString() + : plugin; + instance = await import(esmPath); + } catch (error: any) { + if (error.code === "ERR_UNSUPPORTED_DIR_IMPORT") { + // eslint-disable-next-line @typescript-eslint/no-require-imports + instance = require(plugin); + } else { + throw error; + } } + initFunction = instance.load; } - const initFunction = instance.load; if (typeof initFunction === "function") { await initFunction(app); @@ -54,7 +61,11 @@ export async function loadPlugins( } } -function getPluginDisplayName(plugin: string) { +function getPluginDisplayName(plugin: NormalizedPathOrModuleOrFunction) { + if (typeof plugin === "function") { + return plugin.name || "function"; + } + const path = nicePath(plugin); if (path.startsWith("./node_modules/")) { return path.substring("./node_modules/".length); diff --git a/src/lib/utils/sort.ts b/src/lib/utils/sort.ts index 303f95113..b3d974ca8 100644 --- a/src/lib/utils/sort.ts +++ b/src/lib/utils/sort.ts @@ -163,7 +163,11 @@ const sorts: Record< }, }; -export function getSortFunction(opts: Options) { +export function isValidSortStrategy(strategy: string): strategy is SortStrategy { + return SORT_STRATEGIES.includes(strategy as never); +} + +export function getSortFunction(opts: Options, strategies: readonly SortStrategy[] = opts.getValue("sort")) { const kindSortOrder = opts .getValue("kindSortOrder") .map((k) => ReflectionKind[k]); @@ -174,7 +178,6 @@ export function getSortFunction(opts: Options) { } } - const strategies = opts.getValue("sort"); const data = { kindSortOrder }; return function sortReflections( diff --git a/src/lib/validation/links.ts b/src/lib/validation/links.ts index ac1b80dc5..e5067d42d 100644 --- a/src/lib/validation/links.ts +++ b/src/lib/validation/links.ts @@ -2,6 +2,7 @@ import { i18n, type Logger } from "#utils"; import { type Comment, type CommentDisplayPart, + type InlineTagDisplayPart, type ProjectReflection, type Reflection, ReflectionKind, @@ -11,7 +12,7 @@ import { const linkTags = ["@link", "@linkcode", "@linkplain"]; function getBrokenPartLinks(parts: readonly CommentDisplayPart[]) { - const links: string[] = []; + const links: InlineTagDisplayPart[] = []; for (const part of parts) { if ( @@ -19,7 +20,7 @@ function getBrokenPartLinks(parts: readonly CommentDisplayPart[]) { linkTags.includes(part.tag) && (!part.target || part.target instanceof ReflectionSymbolId) ) { - links.push(part.text.trim()); + links.push(part); } } @@ -44,30 +45,27 @@ export function validateLinks( for (const id in project.reflections) { checkReflection(project.reflections[id], logger); } - - if (!(project.id in project.reflections)) { - checkReflection(project, logger); - } } function checkReflection(reflection: Reflection, logger: Logger) { if (reflection.isProject() || reflection.isDeclaration()) { for (const broken of getBrokenPartLinks(reflection.readme || [])) { + const linkText = broken.text.trim(); // #2360, "@" is a future reserved character in TSDoc component paths // If a link starts with it, and doesn't include a module source indicator "!" // then the user probably is trying to link to a package containing "@" with an absolute link. - if (broken.startsWith("@") && !broken.includes("!")) { + if (linkText.startsWith("@") && !linkText.includes("!")) { logger.warn( i18n.failed_to_resolve_link_to_0_in_readme_for_1_may_have_meant_2( - broken, + linkText, reflection.getFriendlyFullName(), - broken.replace(/[.#~]/, "!"), + linkText.replace(/[.#~]/, "!"), ), ); } else { logger.warn( i18n.failed_to_resolve_link_to_0_in_readme_for_1( - broken, + linkText, reflection.getFriendlyFullName(), ), ); @@ -77,21 +75,19 @@ function checkReflection(reflection: Reflection, logger: Logger) { if (reflection.isDocument()) { for (const broken of getBrokenPartLinks(reflection.content)) { - // #2360, "@" is a future reserved character in TSDoc component paths - // If a link starts with it, and doesn't include a module source indicator "!" - // then the user probably is trying to link to a package containing "@" with an absolute link. - if (broken.startsWith("@") && !broken.includes("!")) { + const linkText = broken.text.trim(); + if (linkText.startsWith("@") && !linkText.includes("!")) { logger.warn( i18n.failed_to_resolve_link_to_0_in_document_1_may_have_meant_2( - broken, + linkText, reflection.getFriendlyFullName(), - broken.replace(/[.#~]/, "!"), + linkText.replace(/[.#~]/, "!"), ), ); } else { logger.warn( i18n.failed_to_resolve_link_to_0_in_document_1( - broken, + linkText, reflection.getFriendlyFullName(), ), ); @@ -100,25 +96,7 @@ function checkReflection(reflection: Reflection, logger: Logger) { } for (const broken of getBrokenLinks(reflection.comment)) { - // #2360, "@" is a future reserved character in TSDoc component paths - // If a link starts with it, and doesn't include a module source indicator "!" - // then the user probably is trying to link to a package containing "@" with an absolute link. - if (broken.startsWith("@") && !broken.includes("!")) { - logger.warn( - i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2( - broken, - reflection.getFriendlyFullName(), - broken.replace(/[.#~]/, "!"), - ), - ); - } else { - logger.warn( - i18n.failed_to_resolve_link_to_0_in_comment_for_1( - broken, - reflection.getFriendlyFullName(), - ), - ); - } + reportBrokenCommentLink(broken, reflection, logger); } if ( @@ -132,22 +110,35 @@ function checkReflection(reflection: Reflection, logger: Logger) { getBrokenPartLinks, ) ) { - if (broken.startsWith("@") && !broken.includes("!")) { - logger.warn( - i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2( - broken, - reflection.getFriendlyFullName(), - broken.replace(/[.#~]/, "!"), - ), - ); - } else { - logger.warn( - i18n.failed_to_resolve_link_to_0_in_comment_for_1( - broken, - reflection.getFriendlyFullName(), - ), - ); - } + reportBrokenCommentLink(broken, reflection, logger); } } } + +function reportBrokenCommentLink(broken: InlineTagDisplayPart, reflection: Reflection, logger: Logger) { + const linkText = broken.text.trim(); + if (broken.target instanceof ReflectionSymbolId) { + logger.warn( + i18n.comment_for_0_links_to_1_not_included_in_docs_use_external_link_2( + reflection.getFriendlyFullName(), + linkText, + `{ "${broken.target.packageName}": { "${broken.target.qualifiedName}": "#" }}`, + ), + ); + } else if (linkText.startsWith("@") && !linkText.includes("!")) { + logger.warn( + i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2( + linkText, + reflection.getFriendlyFullName(), + linkText.replace(/[.#~]/, "!"), + ), + ); + } else { + logger.warn( + i18n.failed_to_resolve_link_to_0_in_comment_for_1( + linkText, + reflection.getFriendlyFullName(), + ), + ); + } +} diff --git a/src/test/Repository.test.ts b/src/test/Repository.test.ts index a3995d5b0..014addabb 100644 --- a/src/test/Repository.test.ts +++ b/src/test/Repository.test.ts @@ -260,7 +260,7 @@ describe("RepositoryManager - git enabled", () => { it("Handles a nested repository", () => { const sub = normalizePath(join(fix.cwd, "sub_repo/repo.txt")); - const repo = manager.getRepository(sub) as GitRepository; + const repo = manager.getRepository(sub) as GitRepository | undefined; ok(repo); equal(repo.path, normalizePath(join(fix.cwd, "sub_repo"))); equal(repo.getURL(sub, 1), "link:repo.txt"); @@ -314,3 +314,51 @@ describe("RepositoryManager - git enabled", () => { equal(repo.getURL(ign, 1), undefined); }); }); + +describe("RepositoryManager - edge cases", () => { + let fix: Project; + const logger = new TestLogger(); + const manager = new RepositoryManager( + "", + "", + "remote", + "", + false, // disable git + logger, + ); + + beforeEach(() => { + fix = tempdirProject(); + }); + + afterEach(() => { + fix.rm(); + logger.expectNoOtherMessages(); + }); + + it("Handles repositories without any commit", () => { + fix.write(); + git(fix.cwd, "init", "-b", "test"); + equal(manager.getRepository(fix.cwd + "/test.txt"), undefined); + }); + + it("Handles a remote which does not exist", () => { + fix.addFile("test.txt"); + fix.write(); + git(fix.cwd, "init", "-b", "test"); + git(fix.cwd, "add", "."); + git(fix.cwd, "commit", "-m", "test", "--no-gpg-sign"); + equal(manager.getRepository(fix.cwd + "/test.txt"), undefined); + logger.expectMessage('warn: The provided git remote "remote" was not valid. Source links will be broken'); + }); + + it("Handles a remote which does not match a known domain", () => { + fix.addFile("test.txt"); + fix.write(); + git(fix.cwd, "init", "-b", "test"); + git(fix.cwd, "add", "."); + git(fix.cwd, "commit", "-m", "test", "--no-gpg-sign"); + git(fix.cwd, "remote", "add", "remote", "https://example.com/fake.git"); + equal(manager.getRepository(fix.cwd + "/test.txt"), undefined); + }); +}); diff --git a/src/test/TestLogger.ts b/src/test/TestLogger.ts index f94670d1b..0975b1692 100644 --- a/src/test/TestLogger.ts +++ b/src/test/TestLogger.ts @@ -41,10 +41,8 @@ export class TestLogger extends Logger { } } - expectNoOtherMessages({ ignoreDebug } = { ignoreDebug: true }) { - const messages = ignoreDebug - ? this.messages.filter((msg) => !msg.startsWith("debug")) - : this.messages; + expectNoOtherMessages() { + const messages = this.messages.filter((msg) => !msg.startsWith("debug")); ok( messages.length === 0, diff --git a/src/test/behavior.c2.test.ts b/src/test/behavior.c2.test.ts index d410bcd29..c35d2e91a 100644 --- a/src/test/behavior.c2.test.ts +++ b/src/test/behavior.c2.test.ts @@ -887,6 +887,26 @@ describe("Behavior Tests", () => { ]); }); + it("Handles links which do not resolve correctly", () => { + const project = convert("linkResolutionErrors"); + app.options.setValue("validation", { + invalidLink: true, + notDocumented: false, + notExported: false, + }); + + app.validate(project); + logger.expectMessage( + 'warn: The comment for abc links to "Map.size" which was resolved but is not included in the documentation. To fix this warning export it or add { "typescript": { "Map.size": "#" }} to the externalSymbolLinkMappings option', + ); + logger.expectMessage( + 'warn: Failed to resolve link to "DoesNotExist" in comment for abc', + ); + logger.expectMessage( + 'warn: Failed to resolve link to "@typedoc/foo.DoesNotExist" in comment for abc. You may have wanted "@typedoc/foo!DoesNotExist"', + ); + }); + it("Handles merged declarations", () => { const project = convert("mergedDeclarations"); const a = query(project, "SingleCommentMultiDeclaration"); @@ -988,10 +1008,25 @@ describe("Behavior Tests", () => { project.removeReflection(query(project, "nested")); equal( Object.values(project.reflections).map((r) => r.name), - ["typedoc"], + ["typedoc", "Base", "NotHidden", "NotHiddenImpl", "constructor", "NotHiddenImpl"], ); }); + it("Removes heritage clause references to hidden classes.", () => { + const project = convert("removeReflection"); + const Base = query(project, "Base"); + equal(Base.extendedBy, undefined); + equal(Base.implementedBy, undefined); + + const NotHidden = query(project, "NotHidden"); + equal(NotHidden.extendedTypes, undefined); + equal(NotHidden.implementedTypes, undefined); + + const NotHiddenImpl = query(project, "NotHiddenImpl"); + equal(NotHiddenImpl.extendedTypes, undefined); + equal(NotHiddenImpl.implementedTypes, undefined); + }); + it("Handles @see tags", () => { const project = convert("seeTags"); const foo = query(project, "foo"); @@ -1083,36 +1118,17 @@ describe("Behavior Tests", () => { const params = (name: string) => querySig(project, name).parameters?.map((p) => p.name); - equal(params("functionWithADestructuredParameter"), [ - "destructuredParam", - ]); + equal(params("singleParam"), ["params"]); - equal(params("functionWithADestructuredParameterAndExtraParameters"), [ - "__namedParameters", - "extraParameter", - ]); + equal(params("extraParam"), ["params", "extraParameter"]); - equal( - params( - "functionWithADestructuredParameterAndAnExtraParamDirective", - ), - ["__namedParameters"], - ); - - const logs = [ - 'warn: The signature functionWithADestructuredParameterAndExtraParameters has an @param with name "destructuredParam", which was not used', - 'warn: The signature functionWithADestructuredParameterAndExtraParameters has an @param with name "destructuredParam.paramZ", which was not used', - 'warn: The signature functionWithADestructuredParameterAndExtraParameters has an @param with name "destructuredParam.paramG", which was not used', - 'warn: The signature functionWithADestructuredParameterAndExtraParameters has an @param with name "destructuredParam.paramA", which was not used', - 'warn: The signature functionWithADestructuredParameterAndAnExtraParamDirective has an @param with name "fakeParameter", which was not used', - 'warn: The signature functionWithADestructuredParameterAndAnExtraParamDirective has an @param with name "destructuredParam", which was not used', - 'warn: The signature functionWithADestructuredParameterAndAnExtraParamDirective has an @param with name "destructuredParam.paramZ", which was not used', - 'warn: The signature functionWithADestructuredParameterAndAnExtraParamDirective has an @param with name "destructuredParam.paramG", which was not used', - 'warn: The signature functionWithADestructuredParameterAndAnExtraParamDirective has an @param with name "destructuredParam.paramA", which was not used', - ]; - for (const log of logs) { - logger.expectMessage(log); - } + equal(params("extraParamComment"), ["params"]); + + equal(params("multiParam"), ["params", "params2", "__namedParameters"]); + + logger.expectMessage( + 'warn: The signature extraParamComment has an @param with name "fakeParameter", which was not used', + ); logger.expectNoOtherMessages(); }); @@ -1431,17 +1447,66 @@ describe("Behavior Tests", () => { }); it("Supports @document tags", () => { - const project = convert("documentTag"); + const project = convert("documents/docs"); equal(reflToTree(project), { - HasDescriptor: "Interface", + hasDocs: "Variable", }); - const refl = query(project, "HasDescriptor"); - equal(refl.documents?.length, 1); + equal(project.documents?.length, 1); + + equal(project.documents[0].content, [ + { kind: "text", text: "Link to [project](" }, + { kind: "relative-link", target: 1, targetAnchor: undefined, text: "./docs.ts" }, + { kind: "text", text: ")" }, + ]); + + equal(project.readme, [ + { kind: "text", text: "Doc projects readme: [docs](" }, + { kind: "relative-link", target: 2, targetAnchor: undefined, text: "./doc.md" }, + { kind: "text", text: ")" }, + ]); + }); + + it("Supports the @sortStrategy tag #2965", () => { + const project = convert("sortStrategyTag"); + + const getOrder = (name: string) => query(project, name).children?.map(c => c.name); + + equal(getOrder("A"), ["b", "c", "a"]); + equal(getOrder("B"), ["a", "b", "c"]); + equal(getOrder("C"), ["b", "c", "a"]); + + const getGroupOrders = (name: string) => + query(project, name).groups?.map(g => [g.title, g.children.map(c => c.name)]); + + equal(getGroupOrders("A"), [ + ["Variables", ["b", "a"]], + ["Functions", ["c"]], + ]); + + equal(getGroupOrders("B"), [ + ["Variables", ["a", "b"]], + ["Functions", ["c"]], + ]); + + equal(getGroupOrders("C"), [ + ["Variables", ["b", "c"]], + ["Functions", ["a"]], + ]); + + const getCategoryOrders = (name: string) => + query(project, name).categories?.map(g => [g.title, g.children.map(c => c.name)]); - equal(refl.documents[0].content, [ - { kind: "text", text: "External doc!" }, + equal(getCategoryOrders("D"), [ + ["Cat", ["b", "a", "c"]], ]); + + logger.expectMessage( + `warn: Comment for E specifies @sortStrategy with "invalid", which is an invalid sort strategy*`, + ); + logger.expectMessage( + `warn: Comment for E specifies @sortStrategy with "invalid2", which is an invalid sort strategy*`, + ); }); }); diff --git a/src/test/comments.test.ts b/src/test/comments.test.ts index cc1d9c6f1..8a95a98c0 100644 --- a/src/test/comments.test.ts +++ b/src/test/comments.test.ts @@ -5,14 +5,18 @@ import type { CommentParserConfig } from "../lib/converter/comments/index.js"; import { lexBlockComment } from "../lib/converter/comments/blockLexer.js"; import { lexLineComments } from "../lib/converter/comments/lineLexer.js"; import { type Token, TokenSyntaxKind } from "../lib/converter/comments/lexer.js"; -import { parseComment } from "../lib/converter/comments/parser.js"; +import { parseComment, parseCommentString } from "../lib/converter/comments/parser.js"; import { lexCommentString } from "../lib/converter/comments/rawLexer.js"; import { Comment, type CommentDisplayPart, CommentTag } from "../lib/models/index.js"; import { TestLogger } from "./TestLogger.js"; import { extractTagName } from "../lib/converter/comments/tagName.js"; -import { FileRegistry } from "../lib/models/FileRegistry.js"; +import { type FileId, FileRegistry } from "../lib/models/FileRegistry.js"; import { dedent, MinimalSourceFile, type NormalizedPath } from "#utils"; +const neverCalled = () => { + throw new Error("Should not be called"); +}; + describe("Block Comment Lexer", () => { function lex(text: string): Token[] { return Array.from(lexBlockComment(text)); @@ -186,7 +190,7 @@ describe("Block Comment Lexer", () => { }); it("Should recognize tags", () => { - const tokens = lex("/* @tag @a @abc234 */"); + const tokens = lex("/* @tag @a @abc234 @abc-234 */"); equal(tokens, [ { kind: TokenSyntaxKind.Tag, text: "@tag", pos: 3 }, @@ -194,6 +198,8 @@ describe("Block Comment Lexer", () => { { kind: TokenSyntaxKind.Tag, text: "@a", pos: 8 }, { kind: TokenSyntaxKind.Text, text: " ", pos: 10 }, { kind: TokenSyntaxKind.Tag, text: "@abc234", pos: 11 }, + { kind: TokenSyntaxKind.Text, text: " ", pos: 18 }, + { kind: TokenSyntaxKind.Tag, text: "@abc-234", pos: 19 }, ]); }); @@ -641,7 +647,7 @@ describe("Line Comment Lexer", () => { }); it("Should recognize tags", () => { - const tokens = lex("// @tag @a @abc234"); + const tokens = lex("// @tag @a @abc234 @abc-234"); equal(tokens, [ { kind: TokenSyntaxKind.Tag, text: "@tag", pos: 3 }, @@ -649,6 +655,8 @@ describe("Line Comment Lexer", () => { { kind: TokenSyntaxKind.Tag, text: "@a", pos: 8 }, { kind: TokenSyntaxKind.Text, text: " ", pos: 10 }, { kind: TokenSyntaxKind.Tag, text: "@abc234", pos: 11 }, + { kind: TokenSyntaxKind.Text, text: " ", pos: 18 }, + { kind: TokenSyntaxKind.Tag, text: "@abc-234", pos: 19 }, ]); }); @@ -993,12 +1001,12 @@ describe("Raw Lexer", () => { }); it("Should not recognize tags", () => { - const tokens = lex("@123 @@ @ @tag @a @abc234"); + const tokens = lex("@123 @@ @ @tag @a @abc234 @abc-234"); equal(tokens, [ { kind: TokenSyntaxKind.Text, - text: "@123 @@ @ @tag @a @abc234", + text: "@123 @@ @ @tag @a @abc234 @abc-234", pos: 0, }, ]); @@ -1137,6 +1145,7 @@ describe("Comment Parser", () => { "@event", "@packageDocumentation", ]), + preservedTypeAnnotationTags: new Set(["@fires"]), jsDocCompatibility: { defaultTag: true, exampleTag: true, @@ -1155,10 +1164,8 @@ describe("Comment Parser", () => { const content = lexBlockComment(file); const comment = parseComment( content, - config, new MinimalSourceFile(file, "/dev/zero" as NormalizedPath), - logger, - files, + { logger, files, config, createSymbolId: neverCalled }, ); equal( @@ -1182,10 +1189,8 @@ describe("Comment Parser", () => { const content = lexBlockComment(file); const comment = parseComment( content, - config, new MinimalSourceFile(file, "/dev/zero" as NormalizedPath), - logger, - files, + { logger, files, config, createSymbolId: neverCalled }, ); equal( @@ -1210,10 +1215,8 @@ describe("Comment Parser", () => { const content = lexBlockComment(file); const comment = parseComment( content, - config, new MinimalSourceFile(file, "/dev/zero" as NormalizedPath), - logger, - files, + { logger, files, config, createSymbolId: neverCalled }, ); equal( @@ -1238,10 +1241,8 @@ describe("Comment Parser", () => { const content = lexBlockComment(file); const comment = parseComment( content, - config, new MinimalSourceFile(file, "/dev/zero" as NormalizedPath), - logger, - files, + { logger, files, config, createSymbolId: neverCalled }, ); logger.expectMessage( @@ -1258,10 +1259,8 @@ describe("Comment Parser", () => { const content = lexBlockComment(text); const comment = parseComment( content, - config, new MinimalSourceFile(text, "/dev/zero" as NormalizedPath), - logger, - files, + { logger, files, config, createSymbolId: neverCalled }, ); logger.expectNoOtherMessages(); return comment; @@ -1418,14 +1417,14 @@ describe("Comment Parser", () => { { kind: "relative-link", text: "./relative.md", - target: 1, + target: 1 as FileId, targetAnchor: undefined, }, { kind: "text", text: ") ![](" }, { kind: "relative-link", text: "image.png", - target: 2, + target: 2 as FileId, targetAnchor: undefined, }, { @@ -1457,7 +1456,7 @@ describe("Comment Parser", () => { { kind: "relative-link", text: "./relative.md", - target: 1, + target: 1 as FileId, targetAnchor: undefined, }, // Labels can also include single newlines @@ -1467,7 +1466,7 @@ describe("Comment Parser", () => { { kind: "relative-link", text: "./relative.md", - target: 1, + target: 1 as FileId, targetAnchor: undefined, }, // But not double! @@ -1492,7 +1491,7 @@ describe("Comment Parser", () => { { kind: "relative-link", text: "./relative.md", - target: 1, + target: 1 as FileId, targetAnchor: undefined, }, { kind: "text", text: ")" }, @@ -1500,12 +1499,67 @@ describe("Comment Parser", () => { ); }); + it("Recognizes markdown links which contain parentheses and escapes in the label", () => { + const comment = getComment(String.raw`/** + * [(parens) \[brackets\]](./relative.md) + * + * [ + * multi-line + * \[brackets\] + * (parens) + * ]( + * ./relative.md + * ) + */`); + + const link = { + kind: "relative-link", + text: "./relative.md", + target: 1 as FileId, + targetAnchor: undefined, + } as const; + + equal( + comment.summary, + [ + { kind: "text", text: String.raw`[(parens) \[brackets\]](` }, + link, + { kind: "text", text: `)\n\n[\n multi-line\n ${String.raw`\[brackets\]`}\n (parens)\n](\n ` }, + link, + { kind: "text", text: "\n )" }, + ] satisfies CommentDisplayPart[], + ); + }); + + it("Parses markdown link titles with arbitrarily-separated arbitrary combinations of text and code", () => { + const comment = `/** + [text](./1) + [text \`code\`]( \t ./2 ) + [\ntext \`code\`\n](./3) + [\ntext\n\`code\`\n]( ./4 ) + [ \t\`code\` text]( \t./5\t ) + + [\n\n\`code\`](./no1) + [text\n\n](./no2) + [ text\n](\n \n ./no3 \n) + [ text\n\ntext](./no4) + */`; + + const relativeParts = getComment(comment).summary.filter(p => p.kind === "relative-link"); + + for (let i = 0; i < relativeParts.length; i++) { + equal(relativeParts[i].text, `./${i + 1}`); + } + equal(relativeParts.length, 5); + }); + it("Recognizes markdown reference definition blocks", () => { const comment = getComment(`/** * [1]: ./example.md * [2]:<./example with space> * [3]: https://example.com * [4]: #hash + * [^footnote]: ./example.md */`); equal( @@ -1515,19 +1569,19 @@ describe("Comment Parser", () => { { kind: "relative-link", text: "./example.md", - target: 1, + target: 1 as FileId, targetAnchor: undefined, }, { kind: "text", text: "\n[2]:" }, { kind: "relative-link", text: "<./example with space>", - target: 2, + target: 2 as FileId, targetAnchor: undefined, }, { kind: "text", - text: "\n[3]: https://example.com\n[4]: #hash", + text: "\n[3]: https://example.com\n[4]: #hash\n[^footnote]: ./example.md", }, ] satisfies CommentDisplayPart[], ); @@ -1560,14 +1614,14 @@ describe("Comment Parser", () => { { kind: "relative-link", text: "./test.png", - target: 1, + target: 1 as FileId, targetAnchor: undefined, }, { kind: "text", text: '" >\n { ); }); + it("Recognizes HTML picture source srcset links", () => { + const comment = getComment(`/** + * + * + * + * + */`); + + equal( + comment.summary, + [ + { kind: "text", text: '\n\n\n', + }, + ] satisfies CommentDisplayPart[], + ); + }); + + it("Recognizes links", () => { + const comment = getComment(`/** + * + */`); + + equal( + comment.summary, + [ + { kind: "text", text: '' }, + ] satisfies CommentDisplayPart[], + ); + }); + + it("Recognizes HTML audio and video src links", () => { + const comment = getComment(`/** + * + * + * + */`); + + equal( + comment.summary, + [ + { kind: "text", text: '\n\n', + }, + ] satisfies CommentDisplayPart[], + ); + }); + + it("Recognizes img tag with both src and srcset", () => { + const comment = getComment(`/** + * + */`); + + equal( + comment.summary, + [ + { kind: "text", text: '', + }, + ] satisfies CommentDisplayPart[], + ); + }); + it("Recognizes HTML anchor links", () => { const comment = getComment(`/** * @@ -1593,14 +1789,14 @@ describe("Comment Parser", () => { { kind: "relative-link", text: "./test.png", - target: 1, + target: 1 as FileId, targetAnchor: undefined, }, { kind: "text", text: '" >\n { { kind: "relative-link", text: "./path.md#foo", - target: 1, + target: 1 as FileId, targetAnchor: "foo", }, { kind: "text", text: '" >\n[test](' }, { kind: "relative-link", text: "./test.txt#bar", - target: 2, + target: 2 as FileId, targetAnchor: "bar", }, { kind: "text", text: ")" }, @@ -1651,15 +1847,116 @@ describe("Comment Parser", () => { { kind: "relative-link", text: "./&a.png", - target: 1, + target: 1 as FileId, targetAnchor: undefined, }, { kind: "text", text: '" >' }, ] satisfies CommentDisplayPart[], ); - equal(files.getName(1), "&a.png"); - equal(files.getName(1), "&a.png"); + equal(files.getName(1 as FileId), "&a.png"); + equal(files.getName(1 as FileId), "&a.png"); + }); +}); + +describe("Raw Comment Parser", () => { + const config: CommentParserConfig = { + blockTags: new Set([ + "@param", + "@remarks", + "@module", + "@inheritDoc", + "@defaultValue", + ]), + inlineTags: new Set(["@link"]), + modifierTags: new Set([ + "@public", + "@private", + "@protected", + "@readonly", + "@enum", + "@event", + "@packageDocumentation", + ]), + preservedTypeAnnotationTags: new Set(["@fires"]), + jsDocCompatibility: { + defaultTag: true, + exampleTag: true, + ignoreUnescapedBraces: false, + inheritDocTag: false, + }, + suppressCommentWarningsInDeclarationFiles: false, + useTsLinkResolution: false, + commentStyle: "jsdoc", + }; + + function getComment(text: string) { + const files = new FileRegistry(); + const logger = new TestLogger(); + const content = lexCommentString(text); + const comment = parseCommentString( + content, + config, + new MinimalSourceFile(text, "/dev/zero" as NormalizedPath), + logger, + files, + ); + logger.expectNoOtherMessages(); + return comment; + } + + it("Recognizes markdown links which contain parentheses and escapes in the label", () => { + const comment = getComment(dedent(String.raw` + [(parens) \[brackets\]](./relative.md) + + [ + multi-line + \[brackets\] + (parens) + ]( + ./relative.md + ) + `)); + + const link = { + kind: "relative-link", + text: "./relative.md", + target: 1 as FileId, + targetAnchor: undefined, + } as const; + + equal( + comment.content, + [ + { kind: "text", text: String.raw`[(parens) \[brackets\]](` }, + link, + { kind: "text", text: `)\n\n[\n multi-line\n ${String.raw`\[brackets\]`}\n (parens)\n](\n ` }, + link, + { kind: "text", text: "\n )" }, + ] satisfies CommentDisplayPart[], + ); + }); + + it("Parses markdown link titles with arbitrarily-separated arbitrary combinations of text and code", () => { + const comment = `/** + [text](./1) + [text \`code\`]( \t ./2 ) + [\ntext \`code\`\n](./3) + [\ntext\n\`code\`\n]( ./4 ) + [ \t\`code\` text]( \t./5\t ) + + [\n\n\`code\`](./no1) + [text\n\n](./no2) + [ text\n](\n \n ./no3 \n) + [ text\n\ntext](./no4) + */`; + + const relativeParts = getComment(comment).content.filter(p => p.kind === "relative-link"); + + for (let i = 0; i < relativeParts.length; i++) { + equal(relativeParts[i].text, `./${i + 1}`); + } + equal(relativeParts.length, 5); }); }); diff --git a/src/test/converter.test.ts b/src/test/converter.test.ts index bad427dc4..ce1072d2a 100644 --- a/src/test/converter.test.ts +++ b/src/test/converter.test.ts @@ -21,7 +21,6 @@ import type { ModelToObject } from "../lib/serialization/schema.js"; import { getExpandedEntryPointsForPaths, normalizePath } from "../lib/utils/index.js"; import { getConverterApp, getConverterBase, getConverterProgram } from "./programs.js"; import { FileRegistry } from "../lib/models/FileRegistry.js"; -import { ValidatingFileRegistry } from "../lib/utils/ValidatingFileRegistry.js"; const comparisonSerializer = new Serializer(); comparisonSerializer.addSerializer({ @@ -201,7 +200,7 @@ describe("Converter", function () { it(`[${file}] converts fixtures`, function () { before(); resetReflectionID(); - app.files = new ValidatingFileRegistry(); + app.files = new FileRegistry(); const entryPoints = getExpandedEntryPointsForPaths( app.logger, [path], diff --git a/src/test/converter/class/class.ts b/src/test/converter/class/class.ts index e46637b75..a1b693ce2 100644 --- a/src/test/converter/class/class.ts +++ b/src/test/converter/class/class.ts @@ -106,7 +106,7 @@ export interface TestSubClass { mergedMethod(); } -export module TestSubClass { +export namespace TestSubClass { /** * staticMergedMethod short text. */ diff --git a/src/test/converter/class/specs-with-lump-categories.json b/src/test/converter/class/specs-with-lump-categories.json index 96ab944f1..e63c9b6a6 100644 --- a/src/test/converter/class/specs-with-lump-categories.json +++ b/src/test/converter/class/specs-with-lump-categories.json @@ -565,7 +565,7 @@ { "fileName": "class.ts", "line": 109, - "character": 14, + "character": 17, "url": "typedoc://class.ts#L109" } ] @@ -2239,7 +2239,7 @@ { "fileName": "class.ts", "line": 109, - "character": 14, + "character": 17, "url": "typedoc://class.ts#L109" } ], @@ -4824,7 +4824,11 @@ "character": 13, "url": "typedoc://getter-setter.ts#L22" } - ] + ], + "type": { + "type": "intrinsic", + "name": "string" + } }, { "id": 190, diff --git a/src/test/converter/class/specs.json b/src/test/converter/class/specs.json index 96ab944f1..e63c9b6a6 100644 --- a/src/test/converter/class/specs.json +++ b/src/test/converter/class/specs.json @@ -565,7 +565,7 @@ { "fileName": "class.ts", "line": 109, - "character": 14, + "character": 17, "url": "typedoc://class.ts#L109" } ] @@ -2239,7 +2239,7 @@ { "fileName": "class.ts", "line": 109, - "character": 14, + "character": 17, "url": "typedoc://class.ts#L109" } ], @@ -4824,7 +4824,11 @@ "character": 13, "url": "typedoc://getter-setter.ts#L22" } - ] + ], + "type": { + "type": "intrinsic", + "name": "string" + } }, { "id": 190, diff --git a/src/test/converter/comment/comment.ts b/src/test/converter/comment/comment.ts index c8c079e7a..785827ab1 100644 --- a/src/test/converter/comment/comment.ts +++ b/src/test/converter/comment/comment.ts @@ -1,5 +1,5 @@ /** - * Module doc comment with document. + * Module doc comment with document and link to [self](./comment.ts) and {@link https://example.com} * * @document document.md * @packageDocumentation @@ -32,6 +32,8 @@ import "./comment2"; * * @groupDescription Methods * Methods description! + * + * @gh3020 {type annotation} */ export class CommentedClass { /** diff --git a/src/test/converter/comment/specs.json b/src/test/converter/comment/specs.json index 50d02f6c3..509d4b241 100644 --- a/src/test/converter/comment/specs.json +++ b/src/test/converter/comment/specs.json @@ -16,7 +16,22 @@ "summary": [ { "kind": "text", - "text": "Module doc comment with document." + "text": "Module doc comment with document and link to [self](" + }, + { + "kind": "relative-link", + "text": "./comment.ts", + "target": 1 + }, + { + "kind": "text", + "text": ") and " + }, + { + "kind": "inline-tag", + "tag": "@link", + "text": "https://example.com", + "target": "https://example.com" } ] }, @@ -51,6 +66,11 @@ "text": "Methods\nMethods description!" } ] + }, + { + "tag": "@gh3020", + "content": [], + "typeAnnotation": "{type annotation}" } ] }, @@ -94,9 +114,9 @@ "sources": [ { "fileName": "comment.ts", - "line": 40, + "line": 42, "character": 4, - "url": "typedoc://comment.ts#L40" + "url": "typedoc://comment.ts#L42" } ], "type": { @@ -113,9 +133,9 @@ "sources": [ { "fileName": "comment.ts", - "line": 80, + "line": 82, "character": 4, - "url": "typedoc://comment.ts#L80" + "url": "typedoc://comment.ts#L82" } ], "signatures": [ @@ -136,9 +156,9 @@ "sources": [ { "fileName": "comment.ts", - "line": 80, + "line": 82, "character": 4, - "url": "typedoc://comment.ts#L80" + "url": "typedoc://comment.ts#L82" } ], "parameters": [ @@ -202,9 +222,9 @@ "sources": [ { "fileName": "comment.ts", - "line": 36, + "line": 38, "character": 13, - "url": "typedoc://comment.ts#L36" + "url": "typedoc://comment.ts#L38" } ] }, @@ -217,9 +237,9 @@ "sources": [ { "fileName": "comment.ts", - "line": 89, + "line": 91, "character": 12, - "url": "typedoc://comment.ts#L89" + "url": "typedoc://comment.ts#L91" } ], "type": { diff --git a/src/test/converter/enum/enum.ts b/src/test/converter/enum/enum.ts index 5152f41ff..394ee4a47 100644 --- a/src/test/converter/enum/enum.ts +++ b/src/test/converter/enum/enum.ts @@ -41,7 +41,7 @@ export enum ModuleEnum { /** * This is a module extending an enumeration. */ -export module ModuleEnum { +export namespace ModuleEnum { /** * This is a variable appended to an enumeration. */ diff --git a/src/test/converter/enum/specs.json b/src/test/converter/enum/specs.json index d59f01d7e..144d9a4ff 100644 --- a/src/test/converter/enum/specs.json +++ b/src/test/converter/enum/specs.json @@ -117,7 +117,7 @@ { "fileName": "enum.ts", "line": 44, - "character": 14, + "character": 17, "url": "typedoc://enum.ts#L44" } ] @@ -313,7 +313,7 @@ { "fileName": "enum.ts", "line": 44, - "character": 14, + "character": 17, "url": "typedoc://enum.ts#L44" } ] diff --git a/src/test/converter/enum/specs.nodoc.json b/src/test/converter/enum/specs.nodoc.json index d59f01d7e..144d9a4ff 100644 --- a/src/test/converter/enum/specs.nodoc.json +++ b/src/test/converter/enum/specs.nodoc.json @@ -117,7 +117,7 @@ { "fileName": "enum.ts", "line": 44, - "character": 14, + "character": 17, "url": "typedoc://enum.ts#L44" } ] @@ -313,7 +313,7 @@ { "fileName": "enum.ts", "line": 44, - "character": 14, + "character": 17, "url": "typedoc://enum.ts#L44" } ] diff --git a/src/test/converter/function/function.ts b/src/test/converter/function/function.ts index f90d3c5af..077a9a6f7 100644 --- a/src/test/converter/function/function.ts +++ b/src/test/converter/function/function.ts @@ -161,7 +161,7 @@ export function isNonNull(arg: T | null | undefined): arg is T { /** * This is the module extending the function moduleFunction(). */ -export module moduleFunction { +export namespace moduleFunction { /** * This variable is appended to a function. */ diff --git a/src/test/converter/function/specs.json b/src/test/converter/function/specs.json index eb4d6085a..6c66ee7ef 100644 --- a/src/test/converter/function/specs.json +++ b/src/test/converter/function/specs.json @@ -169,7 +169,7 @@ { "fileName": "function.ts", "line": 164, - "character": 14, + "character": 17, "url": "typedoc://function.ts#L164" } ] @@ -841,19 +841,19 @@ "type": "union", "types": [ { - "type": "intrinsic", - "name": "undefined" + "type": "reference", + "target": 47, + "name": "T", + "package": "typedoc", + "refersToTypeParameter": true }, { "type": "literal", "value": null }, { - "type": "reference", - "target": 47, - "name": "T", - "package": "typedoc", - "refersToTypeParameter": true + "type": "intrinsic", + "name": "undefined" } ] } @@ -921,11 +921,11 @@ "types": [ { "type": "intrinsic", - "name": "undefined" + "name": "boolean" }, { "type": "intrinsic", - "name": "boolean" + "name": "undefined" } ] } @@ -1543,19 +1543,19 @@ "type": "union", "types": [ { - "type": "intrinsic", - "name": "undefined" + "type": "reference", + "target": 51, + "name": "T", + "package": "typedoc", + "refersToTypeParameter": true }, { "type": "literal", "value": null }, { - "type": "reference", - "target": 51, - "name": "T", - "package": "typedoc", - "refersToTypeParameter": true + "type": "intrinsic", + "name": "undefined" } ] } @@ -1680,7 +1680,7 @@ { "fileName": "function.ts", "line": 164, - "character": 14, + "character": 17, "url": "typedoc://function.ts#L164" } ], diff --git a/src/test/converter/inheritance/inherit-doc.ts b/src/test/converter/inheritance/inherit-doc.ts index 6e0c495d2..09a330282 100644 --- a/src/test/converter/inheritance/inherit-doc.ts +++ b/src/test/converter/inheritance/inherit-doc.ts @@ -71,7 +71,6 @@ export function functionSource(arg1: T, arg2: T): string { * @typeParam T - This will be inherited * @param arg1 - This will be inherited * @param arg2 - This will be inherited - * @returns This will be inherited */ export function functionTargetLocal(arg1: T, arg2: T) { return ""; diff --git a/src/test/converter/inheritance/specs.json b/src/test/converter/inheritance/specs.json index 4e458b53e..74aabc605 100644 --- a/src/test/converter/inheritance/specs.json +++ b/src/test/converter/inheritance/specs.json @@ -466,9 +466,9 @@ "sources": [ { "fileName": "inherit-doc.ts", - "line": 76, + "line": 75, "character": 16, - "url": "typedoc://inherit-doc.ts#L76" + "url": "typedoc://inherit-doc.ts#L75" } ], "signatures": [ @@ -504,15 +504,6 @@ } ] }, - { - "tag": "@returns", - "content": [ - { - "kind": "text", - "text": "This will be inherited" - } - ] - }, { "tag": "@returns", "content": [ @@ -527,9 +518,9 @@ "sources": [ { "fileName": "inherit-doc.ts", - "line": 76, + "line": 75, "character": 16, - "url": "typedoc://inherit-doc.ts#L76" + "url": "typedoc://inherit-doc.ts#L75" } ], "typeParameters": [ @@ -680,14 +671,16 @@ "inheritedFrom": { "type": "reference", "target": -1, - "name": "My.constructor" + "name": "My.constructor", + "package": "typedoc" } } ], "inheritedFrom": { "type": "reference", "target": -1, - "name": "My.constructor" + "name": "My.constructor", + "package": "typedoc" } }, { diff --git a/src/test/converter/interface/interface-implementation.ts b/src/test/converter/interface/interface-implementation.ts index df9f78650..aefd892fb 100644 --- a/src/test/converter/interface/interface-implementation.ts +++ b/src/test/converter/interface/interface-implementation.ts @@ -1,4 +1,4 @@ -export module Forms { +export namespace Forms { /** * Function signature of an event listener callback */ diff --git a/src/test/converter/interface/specs.json b/src/test/converter/interface/specs.json index b629a2d17..c554d89a9 100644 --- a/src/test/converter/interface/specs.json +++ b/src/test/converter/interface/specs.json @@ -2235,7 +2235,7 @@ { "fileName": "interface-implementation.ts", "line": 1, - "character": 14, + "character": 17, "url": "typedoc://interface-implementation.ts#L1" } ] @@ -2546,8 +2546,8 @@ }, "inheritedFrom": { "type": "reference", - "target": 106, - "name": "Base.base" + "target": 112, + "name": "Child.base" } }, { diff --git a/src/test/converter/mixin/specs.json b/src/test/converter/mixin/specs.json index 146ed2189..9ec8e447f 100644 --- a/src/test/converter/mixin/specs.json +++ b/src/test/converter/mixin/specs.json @@ -426,14 +426,16 @@ "inheritedFrom": { "type": "reference", "target": -1, - "name": "Mixin2(Mixin1Func(Base)).method1" + "name": "Mixin2(Mixin1Func(Base)).method1", + "package": "typedoc" } } ], "inheritedFrom": { "type": "reference", "target": -1, - "name": "Mixin2(Mixin1Func(Base)).method1" + "name": "Mixin2(Mixin1Func(Base)).method1", + "package": "typedoc" } }, { @@ -493,14 +495,16 @@ "inheritedFrom": { "type": "reference", "target": -1, - "name": "Mixin2(Mixin1Func(Base)).method2" + "name": "Mixin2(Mixin1Func(Base)).method2", + "package": "typedoc" } } ], "inheritedFrom": { "type": "reference", "target": -1, - "name": "Mixin2(Mixin1Func(Base)).method2" + "name": "Mixin2(Mixin1Func(Base)).method2", + "package": "typedoc" } } ], @@ -875,14 +879,16 @@ "inheritedFrom": { "type": "reference", "target": -1, - "name": "Mixin.method1" + "name": "Mixin.method1", + "package": "typedoc" } } ], "inheritedFrom": { "type": "reference", "target": -1, - "name": "Mixin.method1" + "name": "Mixin.method1", + "package": "typedoc" } } ], @@ -1139,14 +1145,16 @@ "inheritedFrom": { "type": "reference", "target": -1, - "name": "Mixin.method1" + "name": "Mixin.method1", + "package": "typedoc" } } ], "inheritedFrom": { "type": "reference", "target": -1, - "name": "Mixin.method1" + "name": "Mixin.method1", + "package": "typedoc" } }, { @@ -1206,14 +1214,16 @@ "inheritedFrom": { "type": "reference", "target": -1, - "name": "Mixin.method2" + "name": "Mixin.method2", + "package": "typedoc" } } ], "inheritedFrom": { "type": "reference", "target": -1, - "name": "Mixin.method2" + "name": "Mixin.method2", + "package": "typedoc" } } ], diff --git a/src/test/converter/types/specs.json b/src/test/converter/types/specs.json index 1ee7e6aad..4431c5bab 100644 --- a/src/test/converter/types/specs.json +++ b/src/test/converter/types/specs.json @@ -1339,6 +1339,24 @@ "kind": 2, "flags": {}, "children": [ + { + "id": 68, + "name": "Empty", + "variant": "declaration", + "kind": 2097152, + "flags": {}, + "sources": [ + { + "fileName": "tuple.ts", + "line": 18, + "character": 12, + "url": "typedoc://tuple.ts#L18" + } + ], + "type": { + "type": "tuple" + } + }, { "id": 66, "name": "LeadingRest", @@ -1732,6 +1750,7 @@ { "title": "Type Aliases", "children": [ + 68, 66, 58, 64, @@ -1760,14 +1779,14 @@ ] }, { - "id": 68, + "id": 69, "name": "type-operator", "variant": "declaration", "kind": 2, "flags": {}, "children": [ { - "id": 70, + "id": 71, "name": "B", "variant": "declaration", "kind": 2097152, @@ -1793,14 +1812,14 @@ } }, { - "id": 71, + "id": 72, "name": "C", "variant": "declaration", "kind": 2097152, "flags": {}, "children": [ { - "id": 73, + "id": 74, "name": "prop1", "variant": "declaration", "kind": 1024, @@ -1819,7 +1838,7 @@ } }, { - "id": 74, + "id": 75, "name": "prop2", "variant": "declaration", "kind": 1024, @@ -1842,8 +1861,8 @@ { "title": "Properties", "children": [ - 73, - 74 + 74, + 75 ] } ], @@ -1857,7 +1876,7 @@ ] }, { - "id": 75, + "id": 76, "name": "D", "variant": "declaration", "kind": 2097152, @@ -1875,14 +1894,14 @@ "operator": "keyof", "target": { "type": "reference", - "target": 71, + "target": 72, "name": "C", "package": "typedoc" } } }, { - "id": 69, + "id": 70, "name": "a", "variant": "declaration", "kind": 32, @@ -1912,15 +1931,15 @@ { "title": "Type Aliases", "children": [ - 70, 71, - 75 + 72, + 76 ] }, { "title": "Variables", "children": [ - 69 + 70 ] } ], @@ -1934,14 +1953,14 @@ ] }, { - "id": 76, + "id": 77, "name": "union-or-intersection", "variant": "declaration", "kind": 2, "flags": {}, "children": [ { - "id": 77, + "id": 78, "name": "FirstType", "variant": "declaration", "kind": 256, @@ -1956,7 +1975,7 @@ }, "children": [ { - "id": 78, + "id": 79, "name": "firstProperty", "variant": "declaration", "kind": 1024, @@ -1987,7 +2006,7 @@ { "title": "Properties", "children": [ - 78 + 79 ] } ], @@ -2001,7 +2020,7 @@ ] }, { - "id": 79, + "id": 80, "name": "SecondType", "variant": "declaration", "kind": 256, @@ -2016,7 +2035,7 @@ }, "children": [ { - "id": 80, + "id": 81, "name": "secondProperty", "variant": "declaration", "kind": 1024, @@ -2047,7 +2066,7 @@ { "title": "Properties", "children": [ - 80 + 81 ] } ], @@ -2061,7 +2080,7 @@ ] }, { - "id": 81, + "id": 82, "name": "ThirdType", "variant": "declaration", "kind": 256, @@ -2076,7 +2095,7 @@ }, "children": [ { - "id": 84, + "id": 85, "name": "thirdComplexProperty", "variant": "declaration", "kind": 1024, @@ -2113,13 +2132,13 @@ "types": [ { "type": "reference", - "target": 77, + "target": 78, "name": "FirstType", "package": "typedoc" }, { "type": "reference", - "target": 79, + "target": 80, "name": "SecondType", "package": "typedoc" } @@ -2131,7 +2150,7 @@ } }, { - "id": 83, + "id": 84, "name": "thirdIntersectionProperty", "variant": "declaration", "kind": 1024, @@ -2157,13 +2176,13 @@ "types": [ { "type": "reference", - "target": 77, + "target": 78, "name": "FirstType", "package": "typedoc" }, { "type": "reference", - "target": 81, + "target": 82, "name": "ThirdType", "package": "typedoc" } @@ -2171,7 +2190,7 @@ } }, { - "id": 82, + "id": 83, "name": "thirdUnionProperty", "variant": "declaration", "kind": 1024, @@ -2197,13 +2216,13 @@ "types": [ { "type": "reference", - "target": 77, + "target": 78, "name": "FirstType", "package": "typedoc" }, { "type": "reference", - "target": 79, + "target": 80, "name": "SecondType", "package": "typedoc" } @@ -2215,9 +2234,9 @@ { "title": "Properties", "children": [ + 85, 84, - 83, - 82 + 83 ] } ], @@ -2235,9 +2254,9 @@ { "title": "Interfaces", "children": [ - 77, - 79, - 81 + 78, + 80, + 82 ] } ], @@ -2261,8 +2280,8 @@ 50, 54, 57, - 68, - 76 + 69, + 77 ] } ], @@ -2559,81 +2578,86 @@ "qualifiedName": "leadingRest" }, "68": { + "packageName": "typedoc", + "packagePath": "src/test/converter/types/tuple.ts", + "qualifiedName": "Empty" + }, + "69": { "packageName": "typedoc", "packagePath": "src/test/converter/types/type-operator.ts", "qualifiedName": "" }, - "69": { + "70": { "packageName": "typedoc", "packagePath": "src/test/converter/types/type-operator.ts", "qualifiedName": "a" }, - "70": { + "71": { "packageName": "typedoc", "packagePath": "src/test/converter/types/type-operator.ts", "qualifiedName": "B" }, - "71": { + "72": { "packageName": "typedoc", "packagePath": "src/test/converter/types/type-operator.ts", "qualifiedName": "C" }, - "73": { + "74": { "packageName": "typedoc", "packagePath": "src/test/converter/types/type-operator.ts", "qualifiedName": "__type.prop1" }, - "74": { + "75": { "packageName": "typedoc", "packagePath": "src/test/converter/types/type-operator.ts", "qualifiedName": "__type.prop2" }, - "75": { + "76": { "packageName": "typedoc", "packagePath": "src/test/converter/types/type-operator.ts", "qualifiedName": "D" }, - "76": { + "77": { "packageName": "typedoc", "packagePath": "src/test/converter/types/union-or-intersection.ts", "qualifiedName": "" }, - "77": { + "78": { "packageName": "typedoc", "packagePath": "src/test/converter/types/union-or-intersection.ts", "qualifiedName": "FirstType" }, - "78": { + "79": { "packageName": "typedoc", "packagePath": "src/test/converter/types/union-or-intersection.ts", "qualifiedName": "FirstType.firstProperty" }, - "79": { + "80": { "packageName": "typedoc", "packagePath": "src/test/converter/types/union-or-intersection.ts", "qualifiedName": "SecondType" }, - "80": { + "81": { "packageName": "typedoc", "packagePath": "src/test/converter/types/union-or-intersection.ts", "qualifiedName": "SecondType.secondProperty" }, - "81": { + "82": { "packageName": "typedoc", "packagePath": "src/test/converter/types/union-or-intersection.ts", "qualifiedName": "ThirdType" }, - "82": { + "83": { "packageName": "typedoc", "packagePath": "src/test/converter/types/union-or-intersection.ts", "qualifiedName": "ThirdType.thirdUnionProperty" }, - "83": { + "84": { "packageName": "typedoc", "packagePath": "src/test/converter/types/union-or-intersection.ts", "qualifiedName": "ThirdType.thirdIntersectionProperty" }, - "84": { + "85": { "packageName": "typedoc", "packagePath": "src/test/converter/types/union-or-intersection.ts", "qualifiedName": "ThirdType.thirdComplexProperty" @@ -2657,8 +2681,8 @@ "4": 50, "5": 54, "6": 57, - "7": 68, - "8": 76 + "7": 69, + "8": 77 } } } diff --git a/src/test/converter/types/tuple.ts b/src/test/converter/types/tuple.ts index fe473906b..7bc3116e1 100644 --- a/src/test/converter/types/tuple.ts +++ b/src/test/converter/types/tuple.ts @@ -15,6 +15,8 @@ export type LeadingRest = [...string[], number]; // returnMapped isn't good enough here. export const leadingRest = {} as any as [...string[], number]; +export type Empty = []; + // Helper to force TS to give us types, rather than type nodes, for a given declaration. function returnMapped() { return {} as any as { [K in keyof T]: T[K] }; diff --git a/src/test/converter2/behavior/destructuredParamRenames.ts b/src/test/converter2/behavior/destructuredParamRenames.ts index 437090c80..83fdd1600 100644 --- a/src/test/converter2/behavior/destructuredParamRenames.ts +++ b/src/test/converter2/behavior/destructuredParamRenames.ts @@ -1,95 +1,34 @@ /** - * This is a function with a destructured parameter. - * - * @param destructuredParam - This is the parameter that is destructured. - * @param destructuredParam.paramZ - This is a string parameter. - * @param destructuredParam.paramG - This is a parameter flagged with any. - * This sentence is placed in the next line. - * - * @param destructuredParam.paramA - * This is a **parameter** pointing to an interface. - * - * ``` - * const value:BaseClass = new BaseClass('test'); - * functionWithArguments('arg', 0, value); - * ``` - * - * @returns This is the return value of the function. + * @param params - desc + * @param params.a - paramZ desc */ -export function functionWithADestructuredParameter({ - paramZ, - paramG, - paramA, -}: { - paramZ: string; - paramG: any; - paramA: Object; -}): number { +export function singleParam({ a }: { a: string }) { return 0; } /** - * This is a function with a destructured parameter and additional undocumented parameters. - * The `@param` directives are ignored because we cannot be certain which parameter they refer to. - * - * @param destructuredParam - This is the parameter that is destructured. - * @param destructuredParam.paramZ - This is a string parameter. - * @param destructuredParam.paramG - This is a parameter flagged with any. - * This sentence is placed in the next line. - * - * @param destructuredParam.paramA - * This is a **parameter** pointing to an interface. - * - * ``` - * const value:BaseClass = new BaseClass('test'); - * functionWithArguments('arg', 0, value); - * ``` - * - * @returns This is the return value of the function. + * @param params - desc */ -export function functionWithADestructuredParameterAndExtraParameters( - { - paramZ, - paramG, - paramA, - }: { - paramZ: string; - paramG: any; - paramA: Object; - }, - extraParameter: string, -): number { +export function extraParam({ a }: { a: string }, extraParameter: string) { return 0; } /** - * This is a function with a destructured parameter and an extra `@param` directive with no corresponding parameter. - * The `@param` directives are ignored because we cannot be certain which corresponds to the real parameter. - * - * @param fakeParameter - This directive does not have a corresponding parameter. - * @param destructuredParam - This is the parameter that is destructured. - * @param destructuredParam.paramZ - This is a string parameter. - * @param destructuredParam.paramG - This is a parameter flagged with any. - * This sentence is placed in the next line. - * - * @param destructuredParam.paramA - * This is a **parameter** pointing to an interface. - * - * ``` - * const value:BaseClass = new BaseClass('test'); - * functionWithArguments('arg', 0, value); - * ``` - * - * @returns This is the return value of the function. + * @param params param + * @param fakeParameter param2 */ -export function functionWithADestructuredParameterAndAnExtraParamDirective({ - paramZ, - paramG, - paramA, -}: { - paramZ: string; - paramG: any; - paramA: Object; -}): number { +export function extraParamComment({ a }: { a: string }) { + return 0; +} + +/** + * @param params params + * @param params2 params2 + */ +export function multiParam( + { a }: { a: string }, + { b }: { b: number }, + { c }: { c: boolean }, +) { return 0; } diff --git a/src/test/converter2/behavior/document.md b/src/test/converter2/behavior/document.md deleted file mode 100644 index 794870100..000000000 --- a/src/test/converter2/behavior/document.md +++ /dev/null @@ -1 +0,0 @@ -External doc! diff --git a/src/test/converter2/behavior/documentTag.ts b/src/test/converter2/behavior/documentTag.ts deleted file mode 100644 index 71e00d05a..000000000 --- a/src/test/converter2/behavior/documentTag.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * @document ./document.md - */ -export interface HasDescriptor { -} diff --git a/src/test/converter2/behavior/documents/doc.md b/src/test/converter2/behavior/documents/doc.md new file mode 100644 index 000000000..69daae76d --- /dev/null +++ b/src/test/converter2/behavior/documents/doc.md @@ -0,0 +1 @@ +Link to [project](./docs.ts) diff --git a/src/test/converter2/behavior/documents/docs.ts b/src/test/converter2/behavior/documents/docs.ts new file mode 100644 index 000000000..c73719e9d --- /dev/null +++ b/src/test/converter2/behavior/documents/docs.ts @@ -0,0 +1,8 @@ +/** + * Link to [doc](./doc.md) + * + * @module + * @document doc.md + */ + +export const hasDocs = true; diff --git a/src/test/converter2/behavior/documents/package.json b/src/test/converter2/behavior/documents/package.json new file mode 100644 index 000000000..b724be201 --- /dev/null +++ b/src/test/converter2/behavior/documents/package.json @@ -0,0 +1,3 @@ +{ + "name": "docs" +} diff --git a/src/test/converter2/behavior/documents/readme.md b/src/test/converter2/behavior/documents/readme.md new file mode 100644 index 000000000..811c08d76 --- /dev/null +++ b/src/test/converter2/behavior/documents/readme.md @@ -0,0 +1 @@ +Doc projects readme: [docs](./doc.md) diff --git a/src/test/converter2/behavior/linkResolutionErrors.ts b/src/test/converter2/behavior/linkResolutionErrors.ts new file mode 100644 index 000000000..02e8d9f22 --- /dev/null +++ b/src/test/converter2/behavior/linkResolutionErrors.ts @@ -0,0 +1,6 @@ +/** + * {@link Map.size} TS resolves link, not included in docs #2700 #2967 + * {@link DoesNotExist} Symbol does not exist #2681 + * {@link @typedoc/foo.DoesNotExist} Symbol does not exist, looks like an attempt to link to a package directly #2360 + */ +export const abc = new Map(); diff --git a/src/test/converter2/behavior/removeReflection.ts b/src/test/converter2/behavior/removeReflection.ts index 159f0781c..33d99802f 100644 --- a/src/test/converter2/behavior/removeReflection.ts +++ b/src/test/converter2/behavior/removeReflection.ts @@ -5,3 +5,10 @@ export function foo(first: string, second: string, third: string) { } export function nested(a: 1 | { a; 1 }) {} + +export interface Base {} +/** @hidden */ +export interface Hidden extends Base {} +export interface NotHidden extends Hidden {} + +export class NotHiddenImpl implements Hidden {} diff --git a/src/test/converter2/behavior/sortStrategyTag.ts b/src/test/converter2/behavior/sortStrategyTag.ts new file mode 100644 index 000000000..f217b2e64 --- /dev/null +++ b/src/test/converter2/behavior/sortStrategyTag.ts @@ -0,0 +1,33 @@ +/** @sortStrategy source-order */ +export namespace A { + export const b = 1; + export function c() {} + export const a = 2; +} + +/** @sortStrategy alphabetical */ +export namespace B { + export function c() {} + export const b = 1; + export const a = 1; +} + +// Default is kind then alphabetical +export namespace C { + export const c = 1; + export function a() {} + export const b = 1; +} + +/** @sortStrategy source-order */ +export namespace D { + /** @category Cat */ + export const b = 1; + /** @category Cat */ + export const a = 1; + /** @category Cat */ + export const c = 1; +} + +/** @sortStrategy invalid, source-order, invalid2 */ +export namespace E {} diff --git a/src/test/converter2/issues/gh2631/crlf.md b/src/test/converter2/issues/gh2631/crlf.md index aa988a781..3d0cf7580 100644 --- a/src/test/converter2/issues/gh2631/crlf.md +++ b/src/test/converter2/issues/gh2631/crlf.md @@ -1,5 +1,5 @@ ---- -title: "Windows Line Endings" ---- - -This file contains CRLF line endings +--- +title: "Windows Line Endings" +--- + +This file contains CRLF line endings diff --git a/src/test/converter2/issues/gh2681.ts b/src/test/converter2/issues/gh2681.ts deleted file mode 100644 index 1bf23a79a..000000000 --- a/src/test/converter2/issues/gh2681.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * {@link Generator} - */ -export const bug = 123; diff --git a/src/test/converter2/issues/gh2932.ts b/src/test/converter2/issues/gh2932.ts new file mode 100644 index 000000000..653cf373b --- /dev/null +++ b/src/test/converter2/issues/gh2932.ts @@ -0,0 +1,6 @@ +/** + * @inline + */ +type Vector2D = [start: number, end: number]; + +export function doStuff([start, end]: Vector2D) {} diff --git a/src/test/converter2/issues/gh2937/index.ts b/src/test/converter2/issues/gh2937/index.ts new file mode 100644 index 000000000..3b34129fa --- /dev/null +++ b/src/test/converter2/issues/gh2937/index.ts @@ -0,0 +1,2 @@ +export const excluded = true; +export { notExcluded } from "./not-excluded.js"; diff --git a/src/test/converter2/issues/gh2937/not-excluded.ts b/src/test/converter2/issues/gh2937/not-excluded.ts new file mode 100644 index 000000000..c506c4a71 --- /dev/null +++ b/src/test/converter2/issues/gh2937/not-excluded.ts @@ -0,0 +1 @@ +export const notExcluded = true; diff --git a/src/test/converter2/issues/gh2949.js b/src/test/converter2/issues/gh2949.js new file mode 100644 index 000000000..724ffc586 --- /dev/null +++ b/src/test/converter2/issues/gh2949.js @@ -0,0 +1,5 @@ +/** + * @callback Test + * @returns {Promise<*>} + */ +export {}; diff --git a/src/test/converter2/issues/gh2954.ts b/src/test/converter2/issues/gh2954.ts new file mode 100644 index 000000000..b45a00eb2 --- /dev/null +++ b/src/test/converter2/issues/gh2954.ts @@ -0,0 +1,11 @@ +export type AliasA = Readonly>; + +export type AliasB = Readonly>; + +export type AliasC = Readonly<{}>; + +export interface InterfaceA { + propertyA: AliasA; + propertyB: AliasB; + propertyC: AliasC; +} diff --git a/src/test/converter2/issues/gh2962.ts b/src/test/converter2/issues/gh2962.ts new file mode 100644 index 000000000..9a41ee059 --- /dev/null +++ b/src/test/converter2/issues/gh2962.ts @@ -0,0 +1,18 @@ +class Class { + msg: string; + + constructor(msg: string) { + this.msg = msg; + } +} + +const Var = 123; + +function Func(a: T) {} + +export { type Class, type Func, type Var }; + +class Class2 {} +const Var2 = 123; +function Func2() {} +export type { Class2, Func2, Var2 }; diff --git a/src/test/converter2/issues/gh2964.d.ts b/src/test/converter2/issues/gh2964.d.ts new file mode 100644 index 000000000..4ec53bb25 --- /dev/null +++ b/src/test/converter2/issues/gh2964.d.ts @@ -0,0 +1,17 @@ +declare module "*.gh2964" { + export const data: "string"; +} + +/** @ignore */ +export { data as first } from "test.gh2964"; +/** @ignore */ +export { data as second } from "test.gh2964"; + +/** Comment on variable */ +const third: "not ignored"; +/** @ignore -- does not work as third has a comment */ +export { third }; + +const fourth: "ignored"; +/** @ignore -- works as fourth does not have a comment */ +export { fourth }; diff --git a/src/test/converter2/issues/gh2970.ts b/src/test/converter2/issues/gh2970.ts new file mode 100644 index 000000000..0a357dcd2 --- /dev/null +++ b/src/test/converter2/issues/gh2970.ts @@ -0,0 +1,10 @@ +/** Comment */ +class Class {} + +/** Comment */ +const Var = 123; + +/** Comment */ +function Func() {} + +export type { Class, Func, Var }; diff --git a/src/test/converter2/issues/gh2978.ts b/src/test/converter2/issues/gh2978.ts new file mode 100644 index 000000000..9dd8876aa --- /dev/null +++ b/src/test/converter2/issues/gh2978.ts @@ -0,0 +1,15 @@ +export interface Parent { + prop: string; +} + +export interface Child extends Partial {} + +export type Tricky = Omit & { x: number }; + +export interface HasX { + x: string; +} + +export interface InheritsX extends Tricky { + // InheritsX.x should *not* be linked to HasX.x +} diff --git a/src/test/converter2/issues/gh2994.d.ts b/src/test/converter2/issues/gh2994.d.ts new file mode 100644 index 000000000..9d4b50e16 --- /dev/null +++ b/src/test/converter2/issues/gh2994.d.ts @@ -0,0 +1,13 @@ +/** + * {@link x} <-- should be resolved with TS resolution + * @packageDocumentation + */ + +/** Required comment */ +import { x } from "gh2994"; + +export var y: 2; + +declare module "gh2994" { + var x: 1; +} diff --git a/src/test/converter2/issues/gh2999.ts b/src/test/converter2/issues/gh2999.ts new file mode 100644 index 000000000..fabc7c768 --- /dev/null +++ b/src/test/converter2/issues/gh2999.ts @@ -0,0 +1,10 @@ +/** + * A test object with property a. + */ +const LocalObject = { + a: 1, +}; + +export const Options = { + LocalObject, +}; diff --git a/src/test/converter2/issues/gh3003/enums.ts b/src/test/converter2/issues/gh3003/enums.ts new file mode 100644 index 000000000..32a4410ac --- /dev/null +++ b/src/test/converter2/issues/gh3003/enums.ts @@ -0,0 +1,9 @@ +/** + * This is a custom enum that is exported as a constant object. + * + * @enum + */ +export const CustomEnum = { + /** A */ + A: "A", +} as const; diff --git a/src/test/converter2/issues/gh3003/index.ts b/src/test/converter2/issues/gh3003/index.ts new file mode 100644 index 000000000..142ac290b --- /dev/null +++ b/src/test/converter2/issues/gh3003/index.ts @@ -0,0 +1,17 @@ +import { CustomEnum } from "./enums.js"; + +/** + * This is a second custom enum that is exported as a constant object. + * + * @enum + */ +const CustomEnum2 = { + ...CustomEnum, + D: "D", +} as const; + +/** @namespace */ +export const ExportedObject = { + CustomEnum, + CustomEnum2, +}; diff --git a/src/test/converter2/issues/gh3006/index.ts b/src/test/converter2/issues/gh3006/index.ts new file mode 100644 index 000000000..990e7896a --- /dev/null +++ b/src/test/converter2/issues/gh3006/index.ts @@ -0,0 +1,9 @@ +/** + * @document two words.md + * @module + */ + +/** + * [link](./two%20words.md) + */ +export const x = 1; diff --git a/src/test/converter2/issues/gh3006/two words.md b/src/test/converter2/issues/gh3006/two words.md new file mode 100644 index 000000000..feafbd5dd --- /dev/null +++ b/src/test/converter2/issues/gh3006/two words.md @@ -0,0 +1 @@ +This document's name contains a space, #3006 diff --git a/src/test/converter2/issues/gh3009.ts b/src/test/converter2/issues/gh3009.ts new file mode 100644 index 000000000..ac9e243da --- /dev/null +++ b/src/test/converter2/issues/gh3009.ts @@ -0,0 +1,7 @@ +/** + * [link with base path](src/test/converter2/issues/gh3009.ts) + * [link direct](gh3009.ts) + * + * [asset link](CHANGELOG.md) + */ +export const x = 1; diff --git a/src/test/converter2/issues/gh3012.ts b/src/test/converter2/issues/gh3012.ts new file mode 100644 index 000000000..6a226968d --- /dev/null +++ b/src/test/converter2/issues/gh3012.ts @@ -0,0 +1,12 @@ +/** + * @remarks DictRemarks + */ +export const DictionarySchema = {}; + +/** + * {@inheritDoc DictionarySchema} + * + * @remarks + * Alias of {@link DictionarySchema} + */ +export const NullProtoObjectSchema = DictionarySchema; diff --git a/src/test/converter2/issues/gh3017.ts b/src/test/converter2/issues/gh3017.ts new file mode 100644 index 000000000..83627d4ac --- /dev/null +++ b/src/test/converter2/issues/gh3017.ts @@ -0,0 +1,8 @@ +export class GH3017 { + /** @hidden */ + constructor() {} + #hashPriv = 1; + private priv = 2; + protected prot = 3; + public pub = 4; +} diff --git a/src/test/converter2/issues/gh3019.ts b/src/test/converter2/issues/gh3019.ts new file mode 100644 index 000000000..0dc0ee36c --- /dev/null +++ b/src/test/converter2/issues/gh3019.ts @@ -0,0 +1,4 @@ +class GH3019 { + accessor x: string = ""; + accessor y: number = 123; +} diff --git a/src/test/converter2/issues/gh3020.ts b/src/test/converter2/issues/gh3020.ts new file mode 100644 index 000000000..e27024db3 --- /dev/null +++ b/src/test/converter2/issues/gh3020.ts @@ -0,0 +1,7 @@ +/** + * Component demo. + * + * @fires {CustomEvent<{id: string, source: Element}>} item-click - event when item is clicked. + */ +export class ButtonControlElement extends Object { +} diff --git a/src/test/converter2/issues/gh3024.ts b/src/test/converter2/issues/gh3024.ts new file mode 100644 index 000000000..7d45c6c4f --- /dev/null +++ b/src/test/converter2/issues/gh3024.ts @@ -0,0 +1,5 @@ +export class GH3024 { + method(x: string | undefined) {} + method2(x: string | null) {} + method3(x: string | null | undefined) {} +} diff --git a/src/test/converter2/issues/gh3026.js b/src/test/converter2/issues/gh3026.js new file mode 100644 index 000000000..ecd21a98b --- /dev/null +++ b/src/test/converter2/issues/gh3026.js @@ -0,0 +1,7 @@ +/** + * @param {object} params params + * @param {string} params.y y + * @returns {Promise} + * @this {{ x: string }} this param + */ +export async function extendedResponse({ y }) {} diff --git a/src/test/converter2/renderer/gh2982.ts b/src/test/converter2/renderer/gh2982.ts new file mode 100644 index 000000000..678f957ec --- /dev/null +++ b/src/test/converter2/renderer/gh2982.ts @@ -0,0 +1,6 @@ +export type TMXNode = {} & { + base: T; +}; + +export interface TMXDataNode extends TMXNode<{ extra: any }> { +} diff --git a/src/test/converter2/renderer/gh2995.ts b/src/test/converter2/renderer/gh2995.ts new file mode 100644 index 000000000..09f1a0e0d --- /dev/null +++ b/src/test/converter2/renderer/gh2995.ts @@ -0,0 +1,3 @@ +export interface gh2995 { + optionalMethod?(filter: string, args: string[]): any; +} diff --git a/src/test/converter2/renderer/gh3007.ts b/src/test/converter2/renderer/gh3007.ts new file mode 100644 index 000000000..5b00f7d70 --- /dev/null +++ b/src/test/converter2/renderer/gh3007.ts @@ -0,0 +1,16 @@ +interface MixinConstructor U, U> { + new (...args: ConstructorParameters): U; +} + +export declare class DOMBase { + [Symbol.iterator](): Iterator; +} + +export interface DOMIterable extends Partial> { +} + +declare const DOMClass_base: MixinConstructor & object>; + +export declare class DOMClass extends DOMClass_base { + private constructor(); +} diff --git a/src/test/converter2/renderer/gh3014.ts b/src/test/converter2/renderer/gh3014.ts new file mode 100644 index 000000000..acd3ea124 --- /dev/null +++ b/src/test/converter2/renderer/gh3014.ts @@ -0,0 +1,7 @@ +import { GH3014Base } from "@typedoc/gh3014"; + +export class GH3014 extends GH3014Base { + constructor() { + super(); + } +} diff --git a/src/test/converter2/renderer/index.ts b/src/test/converter2/renderer/index.ts index 92b7b987a..d43e25ccc 100644 --- a/src/test/converter2/renderer/index.ts +++ b/src/test/converter2/renderer/index.ts @@ -68,6 +68,9 @@ export class ModifiersClass { readonly read = 4; /** @deprecated */ dep = 5; + + /** #2934 same page link {@linkcode ModifiersClass} */ + constructor() {} } /** @@ -128,7 +131,13 @@ export interface NoneGroup { /** @disableGroups */ export interface DisabledGroups { a: 1; + /** [link to readme #3006](./index.ts) */ b(): void; } export * as ExpandType from "./expandType"; +export * as GH2982 from "./gh2982"; +export { gh2995 } from "./gh2995"; +export * as GH3007 from "./gh3007"; +export { GH3014 } from "./gh3014"; +export { box as boxAlias }; diff --git a/src/test/converter2/renderer/node_modules/@typedoc/gh3014/index.d.ts b/src/test/converter2/renderer/node_modules/@typedoc/gh3014/index.d.ts new file mode 100644 index 000000000..5eec32d15 --- /dev/null +++ b/src/test/converter2/renderer/node_modules/@typedoc/gh3014/index.d.ts @@ -0,0 +1,3 @@ +export class GH3014Base { + constructor(); +} diff --git a/src/test/converter2/renderer/node_modules/@typedoc/gh3014/package.json b/src/test/converter2/renderer/node_modules/@typedoc/gh3014/package.json new file mode 100644 index 000000000..c204a035b --- /dev/null +++ b/src/test/converter2/renderer/node_modules/@typedoc/gh3014/package.json @@ -0,0 +1,3 @@ +{ + "name": "@typedoc/gh3014" +} diff --git a/src/test/converter2/renderer/renderer-readme.md b/src/test/converter2/renderer/renderer-readme.md new file mode 100644 index 000000000..e6cd8f720 --- /dev/null +++ b/src/test/converter2/renderer/renderer-readme.md @@ -0,0 +1,3 @@ +# gh3023 <test> + +Anchor for above heading should be `gh3023-test` diff --git a/src/test/converter2/typedoc.json b/src/test/converter2/typedoc.json index 029de8ae6..bcdc04f8d 100644 --- a/src/test/converter2/typedoc.json +++ b/src/test/converter2/typedoc.json @@ -15,12 +15,22 @@ "sourceLinkTemplate": "{path}", "excludeExternals": true, "tsconfig": "tsconfig.json", - "validation": true, + "validation": { + "invalidLink": true, + "notDocumented": false, + "notExported": true, + "rewrittenLink": true, + "unusedMergeModuleWith": true + }, "skipErrorChecking": true, "externalSymbolLinkMappings": { // used by {@link !Promise} "global": { "Promise": "#" + }, + "@typedoc/gh3014": { + "GH3014Base": "typedoc://GH3014Base", + "GH3014Base.constructor": "typedoc://GH3014Base.constructor" } } } diff --git a/src/test/converter2/validation/unusedMergeModuleWith.ts b/src/test/converter2/validation/unusedMergeModuleWith.ts new file mode 100644 index 000000000..dce95101e --- /dev/null +++ b/src/test/converter2/validation/unusedMergeModuleWith.ts @@ -0,0 +1,5 @@ +/** + * @module + * @mergeModuleWith notUsed + */ +export const test = 1; diff --git a/src/test/declarationReference.test.ts b/src/test/declarationReference.test.ts index 2f525dddb..a01e8890e 100644 --- a/src/test/declarationReference.test.ts +++ b/src/test/declarationReference.test.ts @@ -1,5 +1,6 @@ import { deepStrictEqual as equal } from "assert"; import { + meaningToString, parseComponent, parseComponentPath, parseDeclarationReference, @@ -226,4 +227,13 @@ describe("Declaration References", () => { }); }); }); + + describe("meaningToString", () => { + it("Converts to string", () => { + equal(meaningToString({ keyword: "class" }), "class"); + equal(meaningToString({ keyword: "class", index: 1 }), "class(1)"); + equal(meaningToString({ label: "X" }), "X"); + equal(meaningToString({}), ""); + }); + }); }); diff --git a/src/test/internationalization.test.ts b/src/test/internationalization.test.ts index 685bf4b1a..1e076520d 100644 --- a/src/test/internationalization.test.ts +++ b/src/test/internationalization.test.ts @@ -18,7 +18,10 @@ allValidTranslationKeys.push( allValidTranslationKeys.push(...inlineTags.map((s) => "tag_" + s.substring(1))); describe("Internationalization", () => { - const inter = new Internationalization(); + let inter: Internationalization; + beforeEach(() => { + inter = new Internationalization(); + }); afterEach(() => inter.setLocale("en")); it("Supports getting the list of supported languages", () => { @@ -26,6 +29,7 @@ describe("Internationalization", () => { ok(langs.includes("en")); ok(langs.includes("ko")); ok(langs.includes("ja")); + ok(langs.includes("de")); }); it("Supports translating without placeholders", () => { @@ -42,6 +46,31 @@ describe("Internationalization", () => { inter.setLocale("zh"); equal(i18n.loaded_plugin_0("X"), "已加载插件 X"); }); + + it("Handles locales which do not exist", () => { + equal(i18n.loaded_plugin_0("X"), "Loaded plugin X"); + inter.setLocale("fakeLocale"); + equal(i18n.loaded_plugin_0("X"), "Loaded plugin X"); + }); + + it("Supports adding translations for loaded locale", () => { + inter.addTranslations("en", { testTranslation: "Test translation" }); + // @ts-expect-error testTranslation isn't a defined translation + equal(i18n.testTranslation(), "Test translation"); + }); + + it("Supports adding translations for not-loaded locale", () => { + inter.addTranslations("fake", { testTranslation: "Fake translation" }); + inter.setLocale("fake"); + // @ts-expect-error testTranslation isn't a defined translation + equal(i18n.testTranslation(), "Fake translation"); + }); + + it("Considers translations which have only been added by plugins to be real", () => { + inter.addTranslations("fake", { testTranslation: "Fake translation" }); + const supported = inter.getSupportedLanguages(); + ok(supported.includes("fake")); + }); }); describe("Locales", () => { diff --git a/src/test/issues.c2.test.ts b/src/test/issues.c2.test.ts index 2070065af..858bda67b 100644 --- a/src/test/issues.c2.test.ts +++ b/src/test/issues.c2.test.ts @@ -9,7 +9,6 @@ import { QueryType, ReferenceReflection, ReflectionKind, - ReflectionSymbolId, ReflectionType, SignatureReflection, UnionType, @@ -18,7 +17,9 @@ import type { InlineTagDisplayPart } from "../lib/models/Comment.js"; import { getConverter2App, getConverter2Project } from "./programs.js"; import { TestLogger } from "./TestLogger.js"; import { equalKind, getComment, getLinks, getSigComment, query, querySig, reflToTree } from "./utils.js"; -import { DefaultTheme, KindRouter, PageEvent } from "../index.js"; +import { DefaultTheme, type FileId, KindRouter, PageEvent, ReflectionSymbolId } from "../index.js"; +import { normalizePath, TYPEDOC_ROOT } from "#node-utils"; +import { NormalizedPathUtils } from "#utils"; const app = getConverter2App(); @@ -281,7 +282,7 @@ describe("Issue Tests", () => { const project = convert(); const nullableParam = query(project, "nullable").signatures?.[0] ?.parameters?.[0]; - equal(nullableParam?.type?.toString(), "null | string"); + equal(nullableParam?.type?.toString(), "string | null"); const nonNullableParam = query(project, "nonNullable").signatures?.[0] ?.parameters?.[0]; @@ -553,6 +554,7 @@ describe("Issue Tests", () => { }); it("#1898", () => { + app.options.setValue("validation", true); const project = convert(); app.validate(project); logger.expectMessage( @@ -1670,16 +1672,6 @@ describe("Issue Tests", () => { logger.expectNoOtherMessages(); }); - it("#2681 reports warnings on @link tags which resolve to a type not included in the documentation", () => { - const project = convert(); - app.options.setValue("validation", false); - app.options.setValue("validation", { invalidLink: true }); - app.validate(project); - logger.expectMessage( - 'warn: Failed to resolve link to "Generator" in comment for bug', - ); - }); - it("#2683 supports @param on parameters with functions", () => { const project = convert(); const action = querySig(project, "action"); @@ -1732,31 +1724,7 @@ describe("Issue Tests", () => { ); }); - it("#2700a correctly parses links to global properties", () => { - const project = convert(); - app.options.setValue("validation", { - invalidLink: true, - notDocumented: false, - notExported: false, - }); - - app.validate(project); - logger.expectMessage( - 'warn: Failed to resolve link to "Map.size | size user specified" in comment for abc', - ); - logger.expectMessage( - 'warn: Failed to resolve link to "Map.size user specified" in comment for abc', - ); - logger.expectMessage( - 'warn: Failed to resolve link to "Map.size" in comment for abc', - ); - - const abc = query(project, "abc"); - const link = abc.comment?.summary.find((c) => c.kind === "inline-tag"); - ok(link?.target instanceof ReflectionSymbolId); - }); - - it("#2700b respects user specified link text when resolving external links", () => { + it("#2700 respects user specified link text when resolving external links", () => { const project = convert(); const abc = query(project, "abc"); @@ -1769,7 +1737,7 @@ describe("Issue Tests", () => { caption: "resolver caption", }; }); - app.converter.resolveLinks(abc.comment, abc); + app.converter.resolveLinks(abc); app.converter["_externalSymbolResolvers"] = resolvers; equal(getLinks(abc), [ @@ -2115,4 +2083,193 @@ describe("Issue Tests", () => { const EdgeCases = query(project, "EdgeCases"); equal(EdgeCases.typeParameters?.map(t => t.type?.toString()), ["number", undefined]); }); + + it("#2932 handles @inline on tuple types", () => { + const project = convert(); + const sig = querySig(project, "doStuff"); + equal(sig.parameters?.[0].type?.toString(), "[start: number, end: number]"); + }); + + it("#2937 resolves symbols before checking if they are excluded/external", () => { + app.options.setValue("exclude", ["!**/not-excluded.ts"]); + const project = convert(); + equal(project.children?.map(c => c.name), ["notExcluded"]); + }); + + it("#2949 handles JSDoc wildcard types", () => { + const project = convert(); + equal(query(project, "Test").type?.toString(), "() => Promise"); + }); + + it("#2954 handles Readonly with Record type", () => { + const project = convert(); + equal(query(project, "InterfaceA.propertyA").type?.toString(), "AliasA"); + equal(query(project, "InterfaceA.propertyB").type?.toString(), "AliasB"); + equal(query(project, "InterfaceA.propertyC").type?.toString(), "AliasC"); + }); + + it("#2962 handles type-only exports", () => { + const project = convert(); + equal(project.children?.map(c => [c.name, ReflectionKind[c.kind]]), [ + ["Class", "Interface"], + ["Class2", "Interface"], + ["Func", "TypeAlias"], + ["Func2", "TypeAlias"], + ["Var", "TypeAlias"], + ["Var2", "TypeAlias"], + ]); + + equal(query(project, "Class").children?.map(c => c.name), ["msg"]); + equal(query(project, "Func").type?.toString(), "(a: T) => void"); + equal(query(project, "Var").type?.toString(), "123"); + }); + + it("#2964 handles ignored instances of wildcard declared modules", () => { + const project = convert(); + equal(project.children?.map(c => c.name), ["third"]); + }); + + it("#2970 includes comments on type only exports", () => { + const project = convert(); + equal(project.children?.map(c => [c.name, Comment.combineDisplayParts(c.comment?.summary)]), [ + ["Class", "Comment"], + ["Func", "Comment"], + ["Var", "Comment"], + ]); + }); + + it("#2978 handles parent properties through mapped types", () => { + const project = convert(); + const prop = query(project, "Child.prop"); + equal(prop.inheritedFrom?.reflection?.getFullName(), "Parent.prop"); + const x = query(project, "InheritsX.x"); + equal(x.inheritedFrom?.reflection?.getFullName(), undefined); + equal(x.inheritedFrom?.name, "Tricky.x"); + }); + + it("#2994 uses TS link resolution in first comment of file", () => { + const project = convert(); + const link = project.comment?.summary.find(part => part.kind === "inline-tag"); + ok(link?.target instanceof ReflectionSymbolId); + }); + + it("#2999 picks up parent comments from shorthand property assignments", () => { + const project = convert(); + const opts = query(project, "Options"); + equal(opts.type?.type, "reflection"); + const local = opts.type.declaration.getChildByName("LocalObject"); + equal(Comment.combineDisplayParts(local?.comment?.summary), "A test object with property a."); + }); + + it("#3003 handles @enum on properties within @namespace", () => { + const project = convert(); + + const a = query(project, "ExportedObject.CustomEnum"); + equalKind(a, ReflectionKind.Enum); + equal(a.children?.map(c => c.name), ["A"]); + equal(a.children?.map(c => c.type?.toString()), ['"A"']); + const b = query(project, "ExportedObject.CustomEnum2"); + equalKind(b, ReflectionKind.Enum); + equal(b.children?.map(c => c.name), ["A", "D"]); + equal(b.children?.map(c => c.type?.toString()), ['"A"', '"D"']); + }); + + it("#3006 handles documents containing spaces in their names", () => { + const project = convert(); + equal(["two words"], project.documents?.map(doc => doc.name)); + const doc = project.documents?.[0]; + const x = query(project, "x"); + + ok(x.comment?.summary[1].kind === "relative-link"); + ok(x.comment.summary[1].target); + ok(project.files.resolve(x.comment.summary[1].target, project) === doc); + }); + + it("#3009 supports a base path for resolving relative links", () => { + app.options.setValue("basePath", TYPEDOC_ROOT); + const project = convert(); + const x = query(project, "x"); + const links = x.comment?.summary.filter(part => part.kind === "relative-link"); + equal(links?.length, 3); + equal(links[0].target, 1); + equal(links[1].target, 1); + equal(links[2].target, 2); + ok(app.files.resolve(1 as FileId, project) == project); + equal( + app.files.resolve(2 as FileId, project), + NormalizedPathUtils.resolve(normalizePath(TYPEDOC_ROOT), normalizePath("CHANGELOG.md")), + ); + }); + + it("#3012 removes @remarks from inheriting comment", () => { + const project = convert(); + const nullProto = query(project, "NullProtoObjectSchema"); + equal(nullProto.comment?.blockTags.map(t => t.tag), ["@remarks"]); + equal(nullProto.comment?.blockTags.map(t => Comment.combineDisplayParts(t.content)), ["DictRemarks"]); + + logger.expectMessage("warn: Content in the @remarks block will be overwritten*"); + }); + + it("#3017 supports excluding #private fields only", () => { + app.options.setValue("excludePrivate", false); + app.options.setValue("excludePrivateClassFields", false); + let project = convert(); + equal(query(project, "GH3017").children?.map(c => c.name), ["#hashPriv", "priv", "prot", "pub"]); + + app.options.setValue("excludePrivate", false); + app.options.setValue("excludePrivateClassFields", true); + project = convert(); + equal(query(project, "GH3017").children?.map(c => c.name), ["priv", "prot", "pub"]); + + app.options.setValue("excludePrivate", true); + app.options.setValue("excludePrivateClassFields", false); + project = convert(); + equal(query(project, "GH3017").children?.map(c => c.name), ["prot", "pub"]); + + app.options.setValue("excludePrivate", true); + app.options.setValue("excludePrivateClassFields", true); + project = convert(); + equal(query(project, "GH3017").children?.map(c => c.name), ["prot", "pub"]); + }); + + it("#3019 correctly parses accessor types", () => { + const project = convert(); + equal(query(project, "GH3019.x").type?.toString(), "string"); + equal(query(project, "GH3019.y").type?.toString(), "number"); + }); + + it("#3020 permits preserving type annotations", () => { + app.options.setValue("blockTags", ["@fires"]); + app.options.setValue("preservedTypeAnnotationTags", ["@fires"]); + const project = convert(); + const btn = query(project, "ButtonControlElement"); + + equal(btn.comment?.blockTags.length, 1); + equal(btn.comment.blockTags[0].tag, "@fires"); + equal(btn.comment.blockTags[0].typeAnnotation, "{CustomEvent<{id: string, source: Element}>}"); + }); + + it("#3024 places null and undefined last in unions converted from types", () => { + const project = convert(); + const method = querySig(project, "GH3024.method"); + equal(method.parameters?.[0].type?.toString(), "string | undefined"); + + const method2 = querySig(project, "GH3024.method2"); + equal(method2.parameters?.[0].type?.toString(), "string | null"); + + const method3 = querySig(project, "GH3024.method3"); + equal(method3.parameters?.[0].type?.toString(), "string | null | undefined"); + }); + + it("#3026 handles @this parameter comments and destructured parameter renames with this parameters", () => { + const project = convert(); + const sig = querySig(project, "extendedResponse"); + equal(sig.parameters?.map(p => p.name), ["this", "params"]); + + const comments = sig.parameters.map(p => Comment.combineDisplayParts(p.comment?.summary)); + equal(comments, ["this param", "params"]); + + const types = sig.parameters.map(p => p.type?.toString()); + equal(types, ["{ x: string }", "{ y: string }"]); + }); }); diff --git a/src/test/merge.test.ts b/src/test/merge.test.ts index 65d58c782..b38356bc2 100644 --- a/src/test/merge.test.ts +++ b/src/test/merge.test.ts @@ -1,8 +1,9 @@ import { deepStrictEqual as equal, ok } from "assert"; import { join } from "path"; -import { Application, type DeclarationReflection, EntryPointStrategy, normalizePath, ReferenceType } from "../index.js"; +import { Application, EntryPointStrategy, normalizePath, ReferenceType } from "../index.js"; import { getConverterBase } from "./programs.js"; import { TestLogger } from "./TestLogger.js"; +import { query } from "./utils.js"; const base = getConverterBase(); @@ -29,12 +30,8 @@ describe("Merging projects", () => { ["alias", "class"], ); - const crossRef = project.getChildByName( - "alias.MergedCrossReference", - ) as DeclarationReflection; - const testClass = project.getChildByName("class.class.TestClass"); - ok(testClass, "Missing test class"); - ok(crossRef, "Missing MergedCrossReference"); + const crossRef = query(project, "alias.MergedCrossReference"); + const testClass = query(project, "class.class.TestClass"); ok(crossRef.type instanceof ReferenceType); ok( diff --git a/src/test/models/comment.test.ts b/src/test/models/comment.test.ts index e302b7c0f..b4e4b91f4 100644 --- a/src/test/models/comment.test.ts +++ b/src/test/models/comment.test.ts @@ -1,6 +1,38 @@ import { deepStrictEqual as equal } from "assert"; import { Comment, type CommentDisplayPart, CommentTag } from "../../index.js"; +describe("Comment.similarTo", () => { + it("Checks for similar summaries", () => { + const a = new Comment([{ kind: "text", text: "a" }]); + const b = new Comment([{ kind: "text", text: "a" }]); + const c = new Comment([{ kind: "text", text: "c" }]); + + equal(a.similarTo(b), true); + equal(a.similarTo(c), false); + }); + + it("Ignores modifier tags", () => { + const a = new Comment([{ kind: "text", text: "a" }]); + a.modifierTags.add("@test"); + const b = new Comment([{ kind: "text", text: "a" }]); + + equal(a.similarTo(b), true); + }); + + it("Checks block tags", () => { + const a = new Comment([], [new CommentTag("@test", [{ kind: "text", text: "a" }])]); + const b = new Comment([], [new CommentTag("@test", [{ kind: "text", text: "a" }])]); + const c = new Comment([], [new CommentTag("@test", [{ kind: "text", text: "c" }])]); + const d = new Comment([], [new CommentTag("@test2", [{ kind: "text", text: "c" }])]); + const e = new Comment(); + + equal(a.similarTo(b), true); + equal(a.similarTo(c), false); + equal(a.similarTo(d), false); + equal(a.similarTo(e), false); + }); +}); + describe("Comment.combineDisplayParts", () => { it("Handles text and code", () => { const parts: CommentDisplayPart[] = [ diff --git a/src/test/models/types.test.ts b/src/test/models/types.test.ts index 516e4990d..d9dd945a5 100644 --- a/src/test/models/types.test.ts +++ b/src/test/models/types.test.ts @@ -266,7 +266,7 @@ describe("Type.toString", () => { const project = new ProjectReflection("test", new FileRegistry()); const type = new T.OptionalType( new T.QueryType( - T.ReferenceType.createResolvedReference("X", -1, project), + T.ReferenceType.createBrokenReference("X", project, undefined), ), ); equal(type.toString(), "typeof X?"); @@ -287,7 +287,7 @@ describe("Type.toString", () => { const project = new ProjectReflection("test", new FileRegistry()); const type = new T.TypeOperatorType( new T.QueryType( - T.ReferenceType.createResolvedReference("X", -1, project), + T.ReferenceType.createBrokenReference("X", project, undefined), ), "keyof", ); diff --git a/src/test/output/Slugger.test.ts b/src/test/output/Slugger.test.ts index f11f9c1b6..f0c839a22 100644 --- a/src/test/output/Slugger.test.ts +++ b/src/test/output/Slugger.test.ts @@ -13,4 +13,15 @@ describe("Slugger", () => { equal(slugger.slug("model"), "model"); equal(slugger.slug("Model"), "Model-1"); }); + + it("Handles embedded html characters", () => { + const slugger = new Slugger({ lowercase: true }); + equal(slugger.slug("test "), "test-t"); + equal(slugger.slug("test "), "test-t-1"); + }); + + it("Handles adjacent whitespace", () => { + const slugger = new Slugger({ lowercase: true }); + equal(slugger.slug("test test2"), "test-test2"); + }); }); diff --git a/src/test/output/formatter.test.ts b/src/test/output/formatter.test.ts index d8b9db60f..cfe2f4a4b 100644 --- a/src/test/output/formatter.test.ts +++ b/src/test/output/formatter.test.ts @@ -228,7 +228,7 @@ describe("Formatter", () => { it("Handles query types", () => { const project = new ProjectReflection("", new FileRegistry()); const type = new QueryType( - ReferenceType.createBrokenReference("x", project), + ReferenceType.createBrokenReference("x", project, undefined), ); const text = renderElementToText(renderType(type)); equal(text, `typeof x`); @@ -236,7 +236,7 @@ describe("Formatter", () => { it("Handles a simple reference type", () => { const project = new ProjectReflection("", new FileRegistry()); - const type = ReferenceType.createBrokenReference("x", project); + const type = ReferenceType.createBrokenReference("x", project, undefined); const text = renderElementToText(renderType(type)); equal(text, `x`); }); @@ -277,7 +277,7 @@ describe("Formatter", () => { it("Handles a reference type pointing to an external url", () => { const project = new ProjectReflection("", new FileRegistry()); - const type = ReferenceType.createBrokenReference("x", project); + const type = ReferenceType.createBrokenReference("x", project, undefined); type.externalUrl = "https://example.com"; const text = renderElementToText(renderType(type)); equal(text, `x`); @@ -285,7 +285,7 @@ describe("Formatter", () => { it("Handles a reference type targeting a type parameter", () => { const project = new ProjectReflection("", new FileRegistry()); - const type = ReferenceType.createBrokenReference("x", project); + const type = ReferenceType.createBrokenReference("x", project, undefined); type.refersToTypeParameter = true; const text = renderElementToText(renderType(type)); equal(text, `x`); @@ -293,7 +293,7 @@ describe("Formatter", () => { it("Handles a reference type with type arguments", () => { const project = new ProjectReflection("", new FileRegistry()); - const type = ReferenceType.createBrokenReference("x", project); + const type = ReferenceType.createBrokenReference("x", project, undefined); type.typeArguments = [ new LiteralType(123), new LiteralType(456), diff --git a/src/test/output/lib.test.tsx b/src/test/output/lib.test.tsx new file mode 100644 index 000000000..9acf34fab --- /dev/null +++ b/src/test/output/lib.test.tsx @@ -0,0 +1,28 @@ +import { deepStrictEqual as equal } from "node:assert"; +import { wbr } from "../../lib/output/themes/lib.js"; +import { JSX } from "#utils"; + +describe("wbr", () => { + it("No breaks", () => { + equal(wbr("hello"), ["hello"]); + }); + + it("Adds to camelCased text", () => { + equal(wbr("helloWorld"), ["hello", , "World"]); + equal(wbr("helloWorldMulti"), ["hello", , "World", , "Multi"]); + }); + + it("Adds to snake_cased text", () => { + equal(wbr("snake_case_text"), ["snake_", , "case_", , "text"]); + equal(wbr("snake__case__text"), ["snake__", , "case__", , "text"]); + }); + + it("Adds to dashed-text", () => { + equal(wbr("dashed-text"), ["dashed-", , "text"]); + }); + + it("Adds appropriately with acronyms", () => { + equal(wbr("HTMLImageElement"), ["HTML", , "Image", , "Element"]); + equal(wbr("theHTMLImageElement"), ["the", , "HTML", , "Image", , "Element"]); + }); +}); diff --git a/src/test/programs.ts b/src/test/programs.ts index 9ccecece5..1074a8cbd 100644 --- a/src/test/programs.ts +++ b/src/test/programs.ts @@ -13,9 +13,8 @@ import { import type { ModelToObject } from "../lib/serialization/schema.js"; import { createAppForTesting } from "../lib/application.js"; import { existsSync } from "fs"; -import { clearCommentCache } from "../lib/converter/comments/index.js"; import { diagnostics } from "../lib/utils/loggers.js"; -import { readFile } from "#node-utils"; +import { normalizePath, OptionDefaults, readFile, ValidatingFileRegistry } from "#node-utils"; let converterApp: Application | undefined; let converterProgram: ts.Program | undefined; @@ -36,6 +35,7 @@ export function getConverterApp() { excludeExternals: true, disableSources: false, excludePrivate: false, + excludePrivateClassFields: false, tsconfig: join(getConverterBase(), "tsconfig.json"), externalPattern: ["**/node_modules/**"], plugin: [], @@ -43,6 +43,8 @@ export function getConverterApp() { gitRevision: "fake", readme: "none", skipErrorChecking: true, + preservedTypeAnnotationTags: ["@gh3020"], + blockTags: [...OptionDefaults.blockTags, "@gh3020"], } satisfies TypeDocOptions, ) ) { @@ -97,7 +99,7 @@ export function getConverterProgram() { const app = getConverterApp(); converterProgram = ts.createProgram( app.options.getFileNames(), - app.options.getCompilerOptions(), + app.options.getCompilerOptions(app.logger), ); const errors = ts.getPreEmitDiagnostics(converterProgram); @@ -135,7 +137,7 @@ export function getConverter2Program() { const app = getConverter2App(); converter2Program = ts.createProgram( app.options.getFileNames(), - app.options.getCompilerOptions(), + app.options.getCompilerOptions(app.logger), ); const errors = ts.getPreEmitDiagnostics(converter2Program); @@ -163,7 +165,8 @@ export function getConverter2Project(entries: string[], folder: string) { join(base, folder, entry), ].find(existsSync) ) - .filter((x) => x !== undefined); + .filter((x) => x !== undefined) + .map(normalizePath); const files = entryPoints.map((e) => program.getSourceFile(e)); for (const [index, file] of files.entries()) { @@ -173,7 +176,7 @@ export function getConverter2Project(entries: string[], folder: string) { ok(entryPoints.length > 0, "Expected at least one entry point"); app.options.setValue("entryPoints", entryPoints); - clearCommentCache(); + app.files = new ValidatingFileRegistry(app.options.getValue("basePath")); return app.converter.convert( files.map((file, index) => { return { diff --git a/src/test/project.test.ts b/src/test/project.test.ts index 9fc79ba33..aaafab3f7 100644 --- a/src/test/project.test.ts +++ b/src/test/project.test.ts @@ -1,5 +1,6 @@ -import { splitUnquotedString } from "../index.js"; +import { JSONOutput, splitUnquotedString } from "../index.js"; import Assert from "assert"; +import { JSON_SCHEMA_VERSION } from "../lib/models/ProjectReflection.js"; describe("Project", function () { describe("splitUnquotedString", () => { @@ -32,4 +33,8 @@ describe("Project", function () { Assert.strictEqual(result[1], "d", "Wrong split"); }); }); + + describe("JSON schema version", () => { + Assert.strictEqual(JSON_SCHEMA_VERSION, JSONOutput.SCHEMA_VERSION); + }); }); diff --git a/src/test/renderer/specs/classes/BaseClass.json b/src/test/renderer/specs/classes/BaseClass.json index 288a179ce..47bf55a43 100644 --- a/src/test/renderer/specs/classes/BaseClass.json +++ b/src/test/renderer/specs/classes/BaseClass.json @@ -401,7 +401,9 @@ { "div.tsd-description": [ { - "div.tsd-comment.tsd-typography": "

    Base class method

    \n" + "div.tsd-comment.tsd-typography": { + "p": "Base class method" + } }, { "div.tsd-parameters": [ @@ -433,9 +435,6 @@ } ] }, - { - "div.tsd-comment.tsd-typography": [] - }, { "aside.tsd-sources": { "ul": { diff --git a/src/test/renderer/specs/classes/GH3007.DOMBase.json b/src/test/renderer/specs/classes/GH3007.DOMBase.json new file mode 100644 index 000000000..18378c675 --- /dev/null +++ b/src/test/renderer/specs/classes/GH3007.DOMBase.json @@ -0,0 +1,505 @@ +{ + "div.container.container-main": [ + { + "div.col-content": [ + { + "div.tsd-page-title": [ + { + "tag": "ul.tsd-breadcrumb", + "props": { + "aria-label": "Breadcrumb" + }, + "children": [ + { + "li": { + "tag": "a", + "props": { + "href": "../modules/GH3007.json" + }, + "children": "GH3007" + } + }, + { + "li": { + "tag": "a", + "props": { + "href": "", + "aria-current": "page" + }, + "children": "DOMBase" + } + } + ] + }, + { + "h1": "Class DOMBase" + } + ] + }, + { + "section.tsd-panel": [ + { + "h4": "Type Parameters" + }, + { + "ul.tsd-type-parameter-list": { + "li": { + "span#t": [ + { + "span.tsd-kind-type-parameter": "T" + }, + " ", + { + "span.tsd-signature-keyword": "extends" + }, + " ", + { + "span.tsd-signature-type": "Node" + } + ] + } + } + } + ] + }, + { + "aside.tsd-sources": { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh3007.ts" + }, + "children": "gh3007.ts:5" + } + ] + } + } + }, + { + "section.tsd-panel-group.tsd-index-group": { + "section.tsd-panel.tsd-index-panel": { + "tag": "details.tsd-index-content.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary.tsd-index-summary": { + "h5.tsd-index-heading.uppercase": "Index" + } + }, + { + "div.tsd-accordion-details": [ + { + "section.tsd-index-section": [ + { + "h3.tsd-index-heading": "Constructors" + }, + { + "div.tsd-index-list": [ + { + "tag": "a.tsd-index-link", + "props": { + "href": "#constructor" + }, + "children": { + "span": "constructor" + } + }, + "\n" + ] + } + ] + }, + { + "section.tsd-index-section": [ + { + "h3.tsd-index-heading": "Methods" + }, + { + "div.tsd-index-list": [ + { + "tag": "a.tsd-index-link", + "props": { + "href": "#iterator" + }, + "children": { + "span": "[iterator]" + } + }, + "\n" + ] + } + ] + } + ] + } + ] + } + } + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Constructors" + }, + "children": { + "h2": "Constructors" + } + }, + { + "section": { + "section.tsd-panel.tsd-member": [ + { + "h3.tsd-anchor-link#constructor": [ + { + "span": "constructor" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#constructor", + "aria-label": "Permalink" + } + } + ] + }, + { + "ul.tsd-signatures": { + "li.": [ + { + "div.tsd-signature.tsd-anchor-link#constructordombase": [ + { + "span.tsd-signature-keyword": "new" + }, + " ", + { + "span.tsd-kind-constructor-signature": "DOMBase" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "tag": "a.tsd-signature-type.tsd-kind-type-parameter", + "props": { + "href": "#constructordombaset" + }, + "children": "T" + }, + " ", + { + "span.tsd-signature-keyword": "extends" + }, + " ", + { + "span.tsd-signature-type": "Node" + }, + { + "span.tsd-signature-symbol": ">" + }, + { + "span.tsd-signature-symbol": "()" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "tag": "a.tsd-signature-type.tsd-kind-class", + "props": { + "href": "" + }, + "children": "DOMBase" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "tag": "a.tsd-signature-type.tsd-kind-type-parameter", + "props": { + "href": "#constructordombaset" + }, + "children": "T" + }, + { + "span.tsd-signature-symbol": ">" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#constructordombase", + "aria-label": "Permalink" + } + } + ] + }, + { + "div.tsd-description": [ + { + "section.tsd-panel": [ + { + "h4": "Type Parameters" + }, + { + "ul.tsd-type-parameter-list": { + "li": { + "span#constructordombaset": [ + { + "span.tsd-kind-type-parameter": "T" + }, + " ", + { + "span.tsd-signature-keyword": "extends" + }, + " ", + { + "span.tsd-signature-type": "Node" + } + ] + } + } + } + ] + }, + { + "h4.tsd-returns-title": [ + "Returns ", + { + "tag": "a.tsd-signature-type.tsd-kind-class", + "props": { + "href": "" + }, + "children": "DOMBase" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "tag": "a.tsd-signature-type.tsd-kind-type-parameter", + "props": { + "href": "#constructordombaset" + }, + "children": "T" + }, + { + "span.tsd-signature-symbol": ">" + } + ] + } + ] + } + ] + } + } + ] + } + } + ] + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Methods" + }, + "children": { + "h2": "Methods" + } + }, + { + "section": { + "section.tsd-panel.tsd-member": [ + { + "h3.tsd-anchor-link#iterator": [ + { + "span": "[iterator]" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#iterator", + "aria-label": "Permalink" + } + } + ] + }, + { + "ul.tsd-signatures": { + "li.": [ + { + "div.tsd-signature.tsd-anchor-link#iterator-1": [ + { + "span.tsd-kind-call-signature": "\"[iterator]\"" + }, + { + "span.tsd-signature-symbol": "()" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-type": "Iterator" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "tag": "a.tsd-signature-type.tsd-kind-type-parameter", + "props": { + "href": "#constructordombaset" + }, + "children": "T" + }, + { + "span.tsd-signature-symbol": ">" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#iterator-1", + "aria-label": "Permalink" + } + } + ] + }, + { + "div.tsd-description": [ + { + "h4.tsd-returns-title": [ + "Returns ", + { + "span.tsd-signature-type": "Iterator" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "tag": "a.tsd-signature-type.tsd-kind-type-parameter", + "props": { + "href": "#constructordombaset" + }, + "children": "T" + }, + { + "span.tsd-signature-symbol": ">" + } + ] + }, + { + "aside.tsd-sources": { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh3007.ts" + }, + "children": "gh3007.ts:6" + } + ] + } + } + } + ] + } + ] + } + } + ] + } + } + ] + } + ] + }, + { + "div.col-sidebar": { + "div.page-menu": { + "tag": "details.tsd-accordion.tsd-page-navigation", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary": { + "h3": "On This Page" + } + }, + { + "div.tsd-accordion-details": [ + { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Constructors" + }, + "children": "Constructors" + }, + { + "div": { + "tag": "a", + "props": { + "href": "#constructor" + }, + "children": { + "span": "constructor" + } + } + } + ] + }, + { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Methods" + }, + "children": "Methods" + }, + { + "div": { + "tag": "a", + "props": { + "href": "#iterator" + }, + "children": { + "span": "[iterator]" + } + } + } + ] + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/renderer/specs/classes/GH3007.DOMClass.json b/src/test/renderer/specs/classes/GH3007.DOMClass.json new file mode 100644 index 000000000..8bfe89f19 --- /dev/null +++ b/src/test/renderer/specs/classes/GH3007.DOMClass.json @@ -0,0 +1,288 @@ +{ + "div.container.container-main": [ + { + "div.col-content": [ + { + "div.tsd-page-title": [ + { + "tag": "ul.tsd-breadcrumb", + "props": { + "aria-label": "Breadcrumb" + }, + "children": [ + { + "li": { + "tag": "a", + "props": { + "href": "../modules/GH3007.json" + }, + "children": "GH3007" + } + }, + { + "li": { + "tag": "a", + "props": { + "href": "", + "aria-current": "page" + }, + "children": "DOMClass" + } + } + ] + }, + { + "h1": "Class DOMClass" + } + ] + }, + { + "tag": "section.tsd-panel.tsd-hierarchy", + "props": { + "data-refl": "146" + }, + "children": [ + { + "h4": "Hierarchy" + }, + { + "ul.tsd-hierarchy": { + "li.tsd-hierarchy-item": [ + { + "span.tsd-signature-type": "DOMClass_base" + }, + { + "ul.tsd-hierarchy": { + "li.tsd-hierarchy-item": { + "span.tsd-hierarchy-target": "DOMClass" + } + } + } + ] + } + } + ] + }, + { + "aside.tsd-sources": { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh3007.ts" + }, + "children": "gh3007.ts:14" + } + ] + } + } + }, + { + "section.tsd-panel-group.tsd-index-group": { + "section.tsd-panel.tsd-index-panel": { + "tag": "details.tsd-index-content.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary.tsd-index-summary": { + "h5.tsd-index-heading.uppercase": "Index" + } + }, + { + "div.tsd-accordion-details": { + "section.tsd-index-section": [ + { + "h3.tsd-index-heading": "Methods" + }, + { + "div.tsd-index-list": [ + { + "tag": "a.tsd-index-link.tsd-is-inherited", + "props": { + "href": "#iterator" + }, + "children": { + "span": "[iterator]" + } + }, + "\n" + ] + } + ] + } + } + ] + } + } + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Methods" + }, + "children": { + "h2": "Methods" + } + }, + { + "section": { + "section.tsd-panel.tsd-member.tsd-is-inherited": [ + { + "h3.tsd-anchor-link#iterator": [ + { + "span": "[iterator]" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#iterator", + "aria-label": "Permalink" + } + } + ] + }, + { + "ul.tsd-signatures.tsd-is-inherited": { + "li.tsd-is-inherited": [ + { + "div.tsd-signature.tsd-anchor-link#iterator-1": [ + { + "span.tsd-kind-call-signature": "\"[iterator]\"" + }, + { + "span.tsd-signature-symbol": "()" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-type": "Iterator" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "span.tsd-signature-type": "Node" + }, + { + "span.tsd-signature-symbol": ">" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#iterator-1", + "aria-label": "Permalink" + } + } + ] + }, + { + "div.tsd-description": [ + { + "h4.tsd-returns-title": [ + "Returns ", + { + "span.tsd-signature-type": "Iterator" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "span.tsd-signature-type": "Node" + }, + { + "span.tsd-signature-symbol": ">" + } + ] + }, + { + "aside.tsd-sources": [ + { + "p": "Inherited from DOMClass_base.[iterator]" + }, + { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh3007.ts" + }, + "children": "gh3007.ts:6" + } + ] + } + } + ] + } + ] + } + ] + } + } + ] + } + } + ] + } + ] + }, + { + "div.col-sidebar": { + "div.page-menu": { + "tag": "details.tsd-accordion.tsd-page-navigation", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary": { + "h3": "On This Page" + } + }, + { + "div.tsd-accordion-details": { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Methods" + }, + "children": "Methods" + }, + { + "div": { + "tag": "a.tsd-is-inherited", + "props": { + "href": "#iterator" + }, + "children": { + "span": "[iterator]" + } + } + } + ] + } + } + ] + } + } + } + ] +} diff --git a/src/test/renderer/specs/classes/GH3014.json b/src/test/renderer/specs/classes/GH3014.json new file mode 100644 index 000000000..f764dc955 --- /dev/null +++ b/src/test/renderer/specs/classes/GH3014.json @@ -0,0 +1,286 @@ +{ + "div.container.container-main": [ + { + "div.col-content": [ + { + "div.tsd-page-title": [ + { + "tag": "ul.tsd-breadcrumb", + "props": { + "aria-label": "Breadcrumb" + }, + "children": { + "li": { + "tag": "a", + "props": { + "href": "", + "aria-current": "page" + }, + "children": "GH3014" + } + } + }, + { + "h1": "Class GH3014" + } + ] + }, + { + "tag": "section.tsd-panel.tsd-hierarchy", + "props": { + "data-refl": "151" + }, + "children": [ + { + "h4": "Hierarchy" + }, + { + "ul.tsd-hierarchy": { + "li.tsd-hierarchy-item": [ + { + "tag": "a.tsd-signature-type.external", + "props": { + "href": "typedoc://GH3014Base", + "target": "_blank" + }, + "children": "GH3014Base" + }, + { + "ul.tsd-hierarchy": { + "li.tsd-hierarchy-item": { + "span.tsd-hierarchy-target": "GH3014" + } + } + } + ] + } + } + ] + }, + { + "aside.tsd-sources": { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh3014.ts" + }, + "children": "gh3014.ts:3" + } + ] + } + } + }, + { + "section.tsd-panel-group.tsd-index-group": { + "section.tsd-panel.tsd-index-panel": { + "tag": "details.tsd-index-content.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary.tsd-index-summary": { + "h5.tsd-index-heading.uppercase": "Index" + } + }, + { + "div.tsd-accordion-details": { + "section.tsd-index-section": [ + { + "h3.tsd-index-heading": "Constructors" + }, + { + "div.tsd-index-list": [ + { + "tag": "a.tsd-index-link", + "props": { + "href": "#constructor" + }, + "children": { + "span": "constructor" + } + }, + "\n" + ] + } + ] + } + } + ] + } + } + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Constructors" + }, + "children": { + "h2": "Constructors" + } + }, + { + "section": { + "section.tsd-panel.tsd-member": [ + { + "h3.tsd-anchor-link#constructor": [ + { + "span": "constructor" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#constructor", + "aria-label": "Permalink" + } + } + ] + }, + { + "ul.tsd-signatures": { + "li.": [ + { + "div.tsd-signature.tsd-anchor-link#constructorgh3014": [ + { + "span.tsd-signature-keyword": "new" + }, + " ", + { + "span.tsd-kind-constructor-signature": "GH3014" + }, + { + "span.tsd-signature-symbol": "()" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "tag": "a.tsd-signature-type.tsd-kind-class", + "props": { + "href": "" + }, + "children": "GH3014" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#constructorgh3014", + "aria-label": "Permalink" + } + } + ] + }, + { + "div.tsd-description": [ + { + "h4.tsd-returns-title": [ + "Returns ", + { + "tag": "a.tsd-signature-type.tsd-kind-class", + "props": { + "href": "" + }, + "children": "GH3014" + } + ] + }, + { + "aside.tsd-sources": [ + { + "p": [ + "Overrides ", + { + "tag": "a.external", + "props": { + "href": "typedoc://GH3014Base.constructor", + "target": "_blank" + }, + "children": "GH3014Base.constructor" + } + ] + }, + { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh3014.ts" + }, + "children": "gh3014.ts:4" + } + ] + } + } + ] + } + ] + } + ] + } + } + ] + } + } + ] + } + ] + }, + { + "div.col-sidebar": { + "div.page-menu": { + "tag": "details.tsd-accordion.tsd-page-navigation", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary": { + "h3": "On This Page" + } + }, + { + "div.tsd-accordion-details": { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Constructors" + }, + "children": "Constructors" + }, + { + "div": { + "tag": "a", + "props": { + "href": "#constructor" + }, + "children": { + "span": "constructor" + } + } + } + ] + } + } + ] + } + } + } + ] +} diff --git a/src/test/renderer/specs/classes/GenericClass.json b/src/test/renderer/specs/classes/GenericClass.json index 0322a9cfe..38529a94c 100644 --- a/src/test/renderer/specs/classes/GenericClass.json +++ b/src/test/renderer/specs/classes/GenericClass.json @@ -26,14 +26,11 @@ ] }, { - "section.tsd-panel.tsd-comment": [ - { - "div.tsd-comment.tsd-typography": "

    Generic class

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "section.tsd-panel.tsd-comment": { + "div.tsd-comment.tsd-typography": { + "p": "Generic class" } - ] + } }, { "section.tsd-panel": [ diff --git a/src/test/renderer/specs/classes/ModifiersClass.json b/src/test/renderer/specs/classes/ModifiersClass.json index d2b69f253..e4650c528 100644 --- a/src/test/renderer/specs/classes/ModifiersClass.json +++ b/src/test/renderer/specs/classes/ModifiersClass.json @@ -202,18 +202,52 @@ ] }, { - "div.tsd-description": { - "h4.tsd-returns-title": [ - "Returns ", - { - "tag": "a.tsd-signature-type.tsd-kind-class", - "props": { - "href": "" - }, - "children": "ModifiersClass" + "div.tsd-description": [ + { + "div.tsd-comment.tsd-typography": { + "p": [ + "#2934 same page link", + { + "tag": "a.tsd-kind-class", + "props": { + "href": "#" + }, + "children": { + "code": "ModifiersClass" + } + } + ] } - ] - } + }, + { + "h4.tsd-returns-title": [ + "Returns ", + { + "tag": "a.tsd-signature-type.tsd-kind-class", + "props": { + "href": "" + }, + "children": "ModifiersClass" + } + ] + }, + { + "aside.tsd-sources": { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "index.ts" + }, + "children": "index.ts:73" + } + ] + } + } + } + ] } ] } diff --git a/src/test/renderer/specs/classes/RenderClass.json b/src/test/renderer/specs/classes/RenderClass.json index e87d21064..47a7ca9ee 100644 --- a/src/test/renderer/specs/classes/RenderClass.json +++ b/src/test/renderer/specs/classes/RenderClass.json @@ -26,14 +26,11 @@ ] }, { - "section.tsd-panel.tsd-comment": [ - { - "div.tsd-comment.tsd-typography": "

    Renderer class

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "section.tsd-panel.tsd-comment": { + "div.tsd-comment.tsd-typography": { + "p": "Renderer class" } - ] + } }, { "tag": "section.tsd-panel.tsd-hierarchy", @@ -106,10 +103,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    Index signature

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "Index signature" + } } ] } @@ -356,7 +352,9 @@ { "div.tsd-description": [ { - "div.tsd-comment.tsd-typography": "

    Ctor comment

    \n" + "div.tsd-comment.tsd-typography": { + "p": "Ctor comment" + } }, { "div.tsd-parameters": [ @@ -392,9 +390,6 @@ } ] }, - { - "div.tsd-comment.tsd-typography": [] - }, { "aside.tsd-sources": [ { @@ -493,10 +488,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    Property

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "Property" + } }, { "aside.tsd-sources": { @@ -956,7 +950,9 @@ { "div.tsd-description": [ { - "div.tsd-comment.tsd-typography": "

    Method comment

    \n" + "div.tsd-comment.tsd-typography": { + "p": "Method comment" + } }, { "div.tsd-parameters": [ @@ -988,9 +984,6 @@ } ] }, - { - "div.tsd-comment.tsd-typography": [] - }, { "aside.tsd-sources": [ { @@ -1083,7 +1076,9 @@ { "div.tsd-description": [ { - "div.tsd-comment.tsd-typography": "

    Sig 1 comment

    \n" + "div.tsd-comment.tsd-typography": { + "p": "Sig 1 comment" + } }, { "h4.tsd-returns-title": [ @@ -1093,9 +1088,6 @@ } ] }, - { - "div.tsd-comment.tsd-typography": [] - }, { "aside.tsd-sources": { "ul": { @@ -1158,7 +1150,9 @@ { "div.tsd-description": [ { - "div.tsd-comment.tsd-typography": "

    Sig 2 comment

    \n" + "div.tsd-comment.tsd-typography": { + "p": "Sig 2 comment" + } }, { "div.tsd-parameters": [ @@ -1190,9 +1184,6 @@ } ] }, - { - "div.tsd-comment.tsd-typography": [] - }, { "aside.tsd-sources": { "ul": { diff --git a/src/test/renderer/specs/documents/doc.json b/src/test/renderer/specs/documents/doc.json index e0a58f254..decb74ce4 100644 --- a/src/test/renderer/specs/documents/doc.json +++ b/src/test/renderer/specs/documents/doc.json @@ -21,7 +21,49 @@ } }, { - "div.tsd-panel.tsd-typography": "

    Rendered document

    \n
    \n
    test();\n
    \n\n
    Note

    \nThis is an alert

    \n
    \n" + "div.tsd-panel.tsd-typography": [ + { + "p": "Rendered document" + }, + { + "p": [ + "Link to this doc:", + { + "tag": "a", + "props": { + "href": "" + }, + "children": "link" + } + ] + }, + { + "pre": [ + { + "code.ts": "test();" + }, + { + "tag": "button", + "props": { + "type": "button" + }, + "children": "Copy" + } + ] + }, + { + "div.tsd-alert.tsd-alert-note": [ + { + "div.tsd-alert-title": { + "span": "Note" + } + }, + { + "p": "This is an alert" + } + ] + } + ] } ] }, diff --git a/src/test/renderer/specs/enums/Enumeration.json b/src/test/renderer/specs/enums/Enumeration.json index e5b4f9c9f..966a9f2d8 100644 --- a/src/test/renderer/specs/enums/Enumeration.json +++ b/src/test/renderer/specs/enums/Enumeration.json @@ -28,7 +28,18 @@ { "section.tsd-panel.tsd-comment": [ { - "div.tsd-comment.tsd-typography": "

    Enum comment Value1

    \n" + "div.tsd-comment.tsd-typography": { + "p": [ + "Enum comment", + { + "tag": "a.tsd-kind-enum-member", + "props": { + "href": "#value1" + }, + "children": "Value1" + } + ] + } }, { "div.tsd-comment.tsd-typography": { @@ -45,7 +56,9 @@ } ] }, - "

    Block tag

    \n" + { + "p": "Block tag" + } ] } } @@ -61,7 +74,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:77" + "children": "index.ts:80" } ] } @@ -165,10 +178,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    Value1 comment

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "Value1 comment" + } }, { "aside.tsd-sources": { @@ -180,7 +192,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:79" + "children": "index.ts:82" } ] } @@ -219,10 +231,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    Value2 comment

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "Value2 comment" + } }, { "aside.tsd-sources": { @@ -234,7 +245,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:81" + "children": "index.ts:84" } ] } diff --git a/src/test/renderer/specs/functions/box.json b/src/test/renderer/specs/functions/box.json index b597048fc..473fcb350 100644 --- a/src/test/renderer/specs/functions/box.json +++ b/src/test/renderer/specs/functions/box.json @@ -105,7 +105,9 @@ { "div.tsd-description": [ { - "div.tsd-comment.tsd-typography": "

    Signature comment\n#2921 !Promise

    \n" + "div.tsd-comment.tsd-typography": { + "p": "Signature comment\n#2921 !Promise" + } }, { "section.tsd-panel": [ @@ -147,10 +149,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    Item comment

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "Item comment" + } } ] } @@ -184,9 +185,6 @@ } ] }, - { - "div.tsd-comment.tsd-typography": [] - }, { "aside.tsd-sources": { "ul": { @@ -197,7 +195,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:110" + "children": "index.ts:113" } ] } diff --git a/src/test/renderer/specs/index.json b/src/test/renderer/specs/index.json index 358074286..68faaaf24 100644 --- a/src/test/renderer/specs/index.json +++ b/src/test/renderer/specs/index.json @@ -16,13 +16,57 @@ ] }, { - "div.tsd-panel.tsd-typography": "

    Readme text

    \n" + "div.tsd-panel.tsd-typography": [ + { + "h1#gh3023-test.tsd-anchor-link": [ + "gh3023 <test>", + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#gh3023-test", + "aria-label": "Permalink" + } + } + ] + }, + { + "p": [ + "Anchor for above heading should be", + { + "code": "gh3023-test" + } + ] + } + ] } ] }, { "div.col-sidebar": { - "div.page-menu": [] + "div.page-menu": { + "tag": "details.tsd-accordion.tsd-page-navigation", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary": { + "h3": "On This Page" + } + }, + { + "div.tsd-accordion-details": { + "tag": "a", + "props": { + "href": "#gh3023-test" + }, + "children": { + "span": "gh3023 " + } + } + } + ] + } } } ] diff --git a/src/test/renderer/specs/interfaces/BaseInterface.json b/src/test/renderer/specs/interfaces/BaseInterface.json index 46980afe2..984a18ba0 100644 --- a/src/test/renderer/specs/interfaces/BaseInterface.json +++ b/src/test/renderer/specs/interfaces/BaseInterface.json @@ -41,7 +41,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-call-signature", "props": { @@ -65,7 +65,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-call-signature", "props": { @@ -325,7 +325,9 @@ { "div.tsd-description": [ { - "div.tsd-comment.tsd-typography": "

    Interface method

    \n" + "div.tsd-comment.tsd-typography": { + "p": "Interface method" + } }, { "div.tsd-parameters": [ @@ -357,9 +359,6 @@ } ] }, - { - "div.tsd-comment.tsd-typography": [] - }, { "aside.tsd-sources": { "ul": { diff --git a/src/test/renderer/specs/interfaces/DisabledGroups.json b/src/test/renderer/specs/interfaces/DisabledGroups.json index 187a56a4e..4d7363857 100644 --- a/src/test/renderer/specs/interfaces/DisabledGroups.json +++ b/src/test/renderer/specs/interfaces/DisabledGroups.json @@ -41,7 +41,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -62,7 +62,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-call-signature", "props": { @@ -101,7 +101,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:129" + "children": "index.ts:132" } ] } @@ -194,7 +194,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:130" + "children": "index.ts:133" } ] } @@ -247,6 +247,17 @@ }, { "div.tsd-description": [ + { + "div.tsd-comment.tsd-typography": { + "p": { + "tag": "a", + "props": { + "href": "../" + }, + "children": "link to readme #3006" + } + } + }, { "h4.tsd-returns-title": [ "Returns ", @@ -265,7 +276,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:131" + "children": "index.ts:135" } ] } diff --git a/src/test/renderer/specs/interfaces/ExpandType.ExpandedByDefault.json b/src/test/renderer/specs/interfaces/ExpandType.ExpandedByDefault.json index 4df2574ad..dd0a91628 100644 --- a/src/test/renderer/specs/interfaces/ExpandType.ExpandedByDefault.json +++ b/src/test/renderer/specs/interfaces/ExpandType.ExpandedByDefault.json @@ -52,7 +52,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -181,10 +181,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    B

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "B" + } }, { "aside.tsd-sources": { diff --git a/src/test/renderer/specs/interfaces/GH2982.TMXDataNode.json b/src/test/renderer/specs/interfaces/GH2982.TMXDataNode.json new file mode 100644 index 000000000..4a14d5c79 --- /dev/null +++ b/src/test/renderer/specs/interfaces/GH2982.TMXDataNode.json @@ -0,0 +1,352 @@ +{ + "div.container.container-main": [ + { + "div.col-content": [ + { + "div.tsd-page-title": [ + { + "tag": "ul.tsd-breadcrumb", + "props": { + "aria-label": "Breadcrumb" + }, + "children": [ + { + "li": { + "tag": "a", + "props": { + "href": "../modules/GH2982.json" + }, + "children": "GH2982" + } + }, + { + "li": { + "tag": "a", + "props": { + "href": "", + "aria-current": "page" + }, + "children": "TMXDataNode" + } + } + ] + }, + { + "h1": "Interface TMXDataNode" + } + ] + }, + { + "div.tsd-signature": [ + { + "span.tsd-signature-keyword": "interface" + }, + " ", + { + "span.tsd-kind-interface": "TMXDataNode" + }, + " ", + { + "span.tsd-signature-symbol": "{" + }, + { + "br": [] + }, + " ", + { + "tag": "a.tsd-kind-property", + "props": { + "href": "#base" + }, + "children": "base" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-symbol": "{" + }, + " ", + { + "span.tsd-kind-property": "extra" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-type": "any" + }, + " ", + { + "span.tsd-signature-symbol": "}" + }, + { + "span.tsd-signature-symbol": ";" + }, + { + "br": [] + }, + { + "span.tsd-signature-symbol": "}" + } + ] + }, + { + "tag": "section.tsd-panel.tsd-hierarchy", + "props": { + "data-refl": "123" + }, + "children": [ + { + "h4": [ + "Hierarchy (", + { + "tag": "a", + "props": { + "href": "../hierarchy.html#GH2982.TMXDataNode" + }, + "children": "View Summary" + }, + ")" + ] + }, + { + "ul.tsd-hierarchy": { + "li.tsd-hierarchy-item": [ + { + "tag": "a.tsd-signature-type.tsd-kind-type-alias", + "props": { + "href": "../types/GH2982.TMXNode.json" + }, + "children": "TMXNode" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "span.tsd-signature-symbol": "{" + }, + " ", + { + "span.tsd-kind-property": "extra" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-type": "any" + }, + " ", + { + "span.tsd-signature-symbol": "}" + }, + { + "span.tsd-signature-symbol": ">" + }, + { + "ul.tsd-hierarchy": { + "li.tsd-hierarchy-item": { + "span.tsd-hierarchy-target": "TMXDataNode" + } + } + } + ] + } + } + ] + }, + { + "aside.tsd-sources": { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh2982.ts" + }, + "children": "gh2982.ts:5" + } + ] + } + } + }, + { + "section.tsd-panel-group.tsd-index-group": { + "section.tsd-panel.tsd-index-panel": { + "tag": "details.tsd-index-content.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary.tsd-index-summary": { + "h5.tsd-index-heading.uppercase": "Index" + } + }, + { + "div.tsd-accordion-details": { + "section.tsd-index-section": [ + { + "h3.tsd-index-heading": "Properties" + }, + { + "div.tsd-index-list": [ + { + "tag": "a.tsd-index-link.tsd-is-inherited", + "props": { + "href": "#base" + }, + "children": { + "span": "base" + } + }, + "\n" + ] + } + ] + } + } + ] + } + } + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Properties" + }, + "children": { + "h2": "Properties" + } + }, + { + "section": { + "section.tsd-panel.tsd-member.tsd-is-inherited": [ + { + "h3.tsd-anchor-link#base": [ + { + "span": "base" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#base", + "aria-label": "Permalink" + } + } + ] + }, + { + "div.tsd-signature": [ + { + "span.tsd-kind-property": "base" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-symbol": "{" + }, + " ", + { + "span.tsd-kind-property": "extra" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-type": "any" + }, + " ", + { + "span.tsd-signature-symbol": "}" + } + ] + }, + { + "aside.tsd-sources": [ + { + "p": "Inherited from TMXNode.base" + }, + { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh2982.ts" + }, + "children": "gh2982.ts:2" + } + ] + } + } + ] + } + ] + } + } + ] + } + ] + }, + { + "div.col-sidebar": { + "div.page-menu": { + "tag": "details.tsd-accordion.tsd-page-navigation", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary": { + "h3": "On This Page" + } + }, + { + "div.tsd-accordion-details": { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Properties" + }, + "children": "Properties" + }, + { + "div": { + "tag": "a.tsd-is-inherited", + "props": { + "href": "#base" + }, + "children": { + "span": "base" + } + } + } + ] + } + } + ] + } + } + } + ] +} diff --git a/src/test/renderer/specs/interfaces/GH3007.DOMIterable.json b/src/test/renderer/specs/interfaces/GH3007.DOMIterable.json new file mode 100644 index 000000000..e907f6478 --- /dev/null +++ b/src/test/renderer/specs/interfaces/GH3007.DOMIterable.json @@ -0,0 +1,343 @@ +{ + "div.container.container-main": [ + { + "div.col-content": [ + { + "div.tsd-page-title": [ + { + "tag": "ul.tsd-breadcrumb", + "props": { + "aria-label": "Breadcrumb" + }, + "children": [ + { + "li": { + "tag": "a", + "props": { + "href": "../modules/GH3007.json" + }, + "children": "GH3007" + } + }, + { + "li": { + "tag": "a", + "props": { + "href": "", + "aria-current": "page" + }, + "children": "DOMIterable" + } + } + ] + }, + { + "h1": "Interface DOMIterable" + } + ] + }, + { + "div.tsd-signature": [ + { + "span.tsd-signature-keyword": "interface" + }, + " ", + { + "span.tsd-kind-interface": "DOMIterable" + }, + " ", + { + "span.tsd-signature-symbol": "{" + }, + { + "br": [] + }, + " ", + { + "tag": "a.tsd-kind-property", + "props": { + "href": "#iterator" + }, + "children": "\"[iterator]\"" + }, + { + "span.tsd-signature-symbol": "?:" + }, + " ", + { + "span.tsd-signature-symbol": "()" + }, + " ", + { + "span.tsd-signature-symbol": "=>" + }, + " ", + { + "span.tsd-signature-type": "Iterator" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "span.tsd-signature-type": "Node" + }, + { + "span.tsd-signature-symbol": ">" + }, + { + "span.tsd-signature-symbol": ";" + }, + { + "br": [] + }, + { + "span.tsd-signature-symbol": "}" + } + ] + }, + { + "tag": "section.tsd-panel.tsd-hierarchy", + "props": { + "data-refl": "142" + }, + "children": [ + { + "h4": "Hierarchy" + }, + { + "ul.tsd-hierarchy": { + "li.tsd-hierarchy-item": [ + { + "span.tsd-signature-type": "Partial" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "tag": "a.tsd-signature-type.tsd-kind-class", + "props": { + "href": "../classes/GH3007.DOMBase.json" + }, + "children": "DOMBase" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "span.tsd-signature-type": "Node" + }, + { + "span.tsd-signature-symbol": ">" + }, + { + "span.tsd-signature-symbol": ">" + }, + { + "ul.tsd-hierarchy": { + "li.tsd-hierarchy-item": { + "span.tsd-hierarchy-target": "DOMIterable" + } + } + } + ] + } + } + ] + }, + { + "aside.tsd-sources": { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh3007.ts" + }, + "children": "gh3007.ts:9" + } + ] + } + } + }, + { + "section.tsd-panel-group.tsd-index-group": { + "section.tsd-panel.tsd-index-panel": { + "tag": "details.tsd-index-content.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary.tsd-index-summary": { + "h5.tsd-index-heading.uppercase": "Index" + } + }, + { + "div.tsd-accordion-details": { + "section.tsd-index-section": [ + { + "h3.tsd-index-heading": "Properties" + }, + { + "div.tsd-index-list": [ + { + "tag": "a.tsd-index-link.tsd-is-inherited", + "props": { + "href": "#iterator" + }, + "children": { + "span": "[iterator]?" + } + }, + "\n" + ] + } + ] + } + } + ] + } + } + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Properties" + }, + "children": { + "h2": "Properties" + } + }, + { + "section": { + "section.tsd-panel.tsd-member.tsd-is-inherited": [ + { + "h3.tsd-anchor-link#iterator": [ + { + "code.tsd-tag": "Optional" + }, + { + "span": "[iterator]" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#iterator", + "aria-label": "Permalink" + } + } + ] + }, + { + "div.tsd-signature": [ + { + "span.tsd-kind-property": "\"[iterator]\"" + }, + { + "span.tsd-signature-symbol": "?:" + }, + " ", + { + "span.tsd-signature-symbol": "()" + }, + " ", + { + "span.tsd-signature-symbol": "=>" + }, + " ", + { + "span.tsd-signature-type": "Iterator" + }, + { + "span.tsd-signature-symbol": "<" + }, + { + "span.tsd-signature-type": "Node" + }, + { + "span.tsd-signature-symbol": ">" + } + ] + }, + { + "aside.tsd-sources": [ + { + "p": "Inherited from Partial.[iterator]" + }, + { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh3007.ts" + }, + "children": "gh3007.ts:6" + } + ] + } + } + ] + } + ] + } + } + ] + } + ] + }, + { + "div.col-sidebar": { + "div.page-menu": { + "tag": "details.tsd-accordion.tsd-page-navigation", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary": { + "h3": "On This Page" + } + }, + { + "div.tsd-accordion-details": { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Properties" + }, + "children": "Properties" + }, + { + "div": { + "tag": "a.tsd-is-inherited", + "props": { + "href": "#iterator" + }, + "children": { + "span": "[iterator]" + } + } + } + ] + } + } + ] + } + } + } + ] +} diff --git a/src/test/renderer/specs/interfaces/NoneCategory.json b/src/test/renderer/specs/interfaces/NoneCategory.json index fbc092f71..8f9f12a08 100644 --- a/src/test/renderer/specs/interfaces/NoneCategory.json +++ b/src/test/renderer/specs/interfaces/NoneCategory.json @@ -41,7 +41,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -62,7 +62,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -83,7 +83,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -119,7 +119,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:114" + "children": "index.ts:117" } ] } @@ -235,9 +235,6 @@ } ] }, - { - "div.tsd-comment.tsd-typography": [] - }, { "aside.tsd-sources": { "ul": { @@ -248,7 +245,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:119" + "children": "index.ts:122" } ] } @@ -303,9 +300,6 @@ } ] }, - { - "div.tsd-comment.tsd-typography": [] - }, { "aside.tsd-sources": { "ul": { @@ -316,7 +310,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:116" + "children": "index.ts:119" } ] } @@ -383,7 +377,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:117" + "children": "index.ts:120" } ] } diff --git a/src/test/renderer/specs/interfaces/NoneGroup.json b/src/test/renderer/specs/interfaces/NoneGroup.json index e83fc182e..925274d34 100644 --- a/src/test/renderer/specs/interfaces/NoneGroup.json +++ b/src/test/renderer/specs/interfaces/NoneGroup.json @@ -41,7 +41,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -62,7 +62,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -98,7 +98,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:122" + "children": "index.ts:125" } ] } @@ -193,9 +193,6 @@ } ] }, - { - "div.tsd-comment.tsd-typography": [] - }, { "aside.tsd-sources": { "ul": { @@ -206,7 +203,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:124" + "children": "index.ts:127" } ] } @@ -271,7 +268,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:125" + "children": "index.ts:128" } ] } diff --git a/src/test/renderer/specs/interfaces/gh2995.json b/src/test/renderer/specs/interfaces/gh2995.json new file mode 100644 index 000000000..c4182b433 --- /dev/null +++ b/src/test/renderer/specs/interfaces/gh2995.json @@ -0,0 +1,399 @@ +{ + "div.container.container-main": [ + { + "div.col-content": [ + { + "div.tsd-page-title": [ + { + "tag": "ul.tsd-breadcrumb", + "props": { + "aria-label": "Breadcrumb" + }, + "children": { + "li": { + "tag": "a", + "props": { + "href": "", + "aria-current": "page" + }, + "children": "gh2995" + } + } + }, + { + "h1": "Interface gh2995" + } + ] + }, + { + "div.tsd-signature": [ + { + "span.tsd-signature-keyword": "interface" + }, + " ", + { + "span.tsd-kind-interface": "gh2995" + }, + " ", + { + "span.tsd-signature-symbol": "{" + }, + { + "br": [] + }, + " ", + { + "tag": "a.tsd-kind-call-signature", + "props": { + "href": "#optionalmethod-1" + }, + "children": "optionalMethod" + }, + { + "span.tsd-signature-symbol": "?" + }, + { + "span.tsd-signature-symbol": "(" + }, + { + "span.tsd-kind-parameter": "filter" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-type": "string" + }, + { + "span.tsd-signature-symbol": "," + }, + " ", + { + "span.tsd-kind-parameter": "args" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-type": "string" + }, + { + "span.tsd-signature-symbol": "[]" + }, + { + "span.tsd-signature-symbol": ")" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-type": "any" + }, + { + "span.tsd-signature-symbol": ";" + }, + { + "br": [] + }, + { + "span.tsd-signature-symbol": "}" + } + ] + }, + { + "aside.tsd-sources": { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh2995.ts" + }, + "children": "gh2995.ts:1" + } + ] + } + } + }, + { + "section.tsd-panel-group.tsd-index-group": { + "section.tsd-panel.tsd-index-panel": { + "tag": "details.tsd-index-content.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary.tsd-index-summary": { + "h5.tsd-index-heading.uppercase": "Index" + } + }, + { + "div.tsd-accordion-details": { + "section.tsd-index-section": [ + { + "h3.tsd-index-heading": "Methods" + }, + { + "div.tsd-index-list": [ + { + "tag": "a.tsd-index-link", + "props": { + "href": "#optionalmethod" + }, + "children": { + "span": [ + "optional", + { + "wbr": [] + }, + "Method?" + ] + } + }, + "\n" + ] + } + ] + } + } + ] + } + } + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Methods" + }, + "children": { + "h2": "Methods" + } + }, + { + "section": { + "section.tsd-panel.tsd-member": [ + { + "h3.tsd-anchor-link#optionalmethod": [ + { + "code.tsd-tag": "Optional" + }, + { + "span": [ + "optional", + { + "wbr": [] + }, + "Method" + ] + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#optionalmethod", + "aria-label": "Permalink" + } + } + ] + }, + { + "ul.tsd-signatures": { + "li.": [ + { + "div.tsd-signature.tsd-anchor-link#optionalmethod-1": [ + { + "span.tsd-kind-call-signature": "optionalMethod" + }, + { + "span.tsd-signature-symbol": "?" + }, + { + "span.tsd-signature-symbol": "(" + }, + { + "span.tsd-kind-parameter": "filter" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-type": "string" + }, + { + "span.tsd-signature-symbol": "," + }, + " ", + { + "span.tsd-kind-parameter": "args" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-type": "string" + }, + { + "span.tsd-signature-symbol": "[]" + }, + { + "span.tsd-signature-symbol": ")" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-type": "any" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#optionalmethod-1", + "aria-label": "Permalink" + } + } + ] + }, + { + "div.tsd-description": [ + { + "div.tsd-parameters": [ + { + "h4.tsd-parameters-title": "Parameters" + }, + { + "ul.tsd-parameter-list": [ + { + "li": { + "span": [ + { + "span.tsd-kind-parameter": "filter" + }, + ": ", + { + "span.tsd-signature-type": "string" + } + ] + } + }, + { + "li": { + "span": [ + { + "span.tsd-kind-parameter": "args" + }, + ": ", + { + "span.tsd-signature-type": "string" + }, + { + "span.tsd-signature-symbol": "[]" + } + ] + } + } + ] + } + ] + }, + { + "h4.tsd-returns-title": [ + "Returns ", + { + "span.tsd-signature-type": "any" + } + ] + }, + { + "aside.tsd-sources": { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh2995.ts" + }, + "children": "gh2995.ts:2" + } + ] + } + } + } + ] + } + ] + } + } + ] + } + } + ] + } + ] + }, + { + "div.col-sidebar": { + "div.page-menu": { + "tag": "details.tsd-accordion.tsd-page-navigation", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary": { + "h3": "On This Page" + } + }, + { + "div.tsd-accordion-details": { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Methods" + }, + "children": "Methods" + }, + { + "div": { + "tag": "a", + "props": { + "href": "#optionalmethod" + }, + "children": { + "span": [ + "optional", + { + "wbr": [] + }, + "Method" + ] + } + } + } + ] + } + } + ] + } + } + } + ] +} diff --git a/src/test/renderer/specs/modules.json b/src/test/renderer/specs/modules.json index 21b6084dd..c8247517c 100644 --- a/src/test/renderer/specs/modules.json +++ b/src/test/renderer/specs/modules.json @@ -16,14 +16,11 @@ ] }, { - "section.tsd-panel.tsd-comment": [ - { - "div.tsd-comment.tsd-typography": "

    Module comment

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "section.tsd-panel.tsd-comment": { + "div.tsd-comment.tsd-typography": { + "p": "Module comment" } - ] + } }, { "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", @@ -106,6 +103,52 @@ ] } }, + { + "dd.tsd-member-summary": [] + }, + { + "dt.tsd-member-summary#gh2982": { + "span.tsd-member-summary-name": [ + { + "tag": "a", + "props": { + "href": "modules/GH2982.json" + }, + "children": "GH2982" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#gh2982", + "aria-label": "Permalink" + } + } + ] + } + }, + { + "dd.tsd-member-summary": [] + }, + { + "dt.tsd-member-summary#gh3007": { + "span.tsd-member-summary-name": [ + { + "tag": "a", + "props": { + "href": "modules/GH3007.json" + }, + "children": "GH3007" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#gh3007", + "aria-label": "Permalink" + } + } + ] + } + }, { "dd.tsd-member-summary": [] } @@ -220,6 +263,29 @@ { "dd.tsd-member-summary": [] }, + { + "dt.tsd-member-summary#gh3014": { + "span.tsd-member-summary-name": [ + { + "tag": "a", + "props": { + "href": "classes/GH3014.json" + }, + "children": "GH3014" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#gh3014", + "aria-label": "Permalink" + } + } + ] + } + }, + { + "dd.tsd-member-summary": [] + }, { "dt.tsd-member-summary#modifiersclass": { "span.tsd-member-summary-name": [ @@ -333,6 +399,29 @@ { "dd.tsd-member-summary": [] }, + { + "dt.tsd-member-summary#gh2995": { + "span.tsd-member-summary-name": [ + { + "tag": "a", + "props": { + "href": "interfaces/gh2995.json" + }, + "children": "gh2995" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#gh2995", + "aria-label": "Permalink" + } + } + ] + } + }, + { + "dd.tsd-member-summary": [] + }, { "dt.tsd-member-summary#nonecategory": { "span.tsd-member-summary-name": [ @@ -493,6 +582,56 @@ ] } ] + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-References" + }, + "children": { + "h2": "References" + } + }, + { + "dl.tsd-member-summaries": [ + { + "dt.tsd-member-summary#boxalias": { + "span.tsd-member-summary-name": [ + { + "span": "boxAlias" + }, + { + "span": " → " + }, + { + "tag": "a", + "props": { + "href": "functions/box.json" + }, + "children": "box" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#boxalias", + "aria-label": "Permalink" + } + } + ] + } + }, + { + "dd.tsd-member-summary": [] + } + ] + } + ] } ] }, @@ -551,21 +690,41 @@ "children": "Namespaces" }, { - "div": { - "tag": "a", - "props": { - "href": "#expandtype" + "div": [ + { + "tag": "a", + "props": { + "href": "#expandtype" + }, + "children": { + "span": [ + "Expand", + { + "wbr": [] + }, + "Type" + ] + } }, - "children": { - "span": [ - "Expand", - { - "wbr": [] - }, - "Type" - ] + { + "tag": "a", + "props": { + "href": "#gh2982" + }, + "children": { + "span": "GH2982" + } + }, + { + "tag": "a", + "props": { + "href": "#gh3007" + }, + "children": { + "span": "GH3007" + } } - } + ] } ] }, @@ -640,6 +799,15 @@ ] } }, + { + "tag": "a", + "props": { + "href": "#gh3014" + }, + "children": { + "span": "GH3014" + } + }, { "tag": "a", "props": { @@ -719,6 +887,15 @@ ] } }, + { + "tag": "a", + "props": { + "href": "#gh2995" + }, + "children": { + "span": "gh2995" + } + }, { "tag": "a", "props": { @@ -821,6 +998,38 @@ } } ] + }, + { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-References" + }, + "children": "References" + }, + { + "div": { + "tag": "a", + "props": { + "href": "#boxalias" + }, + "children": { + "span": [ + "box", + { + "wbr": [] + }, + "Alias" + ] + } + } + } + ] } ] } diff --git a/src/test/renderer/specs/modules/ExpandType.NestedBehavior1.json b/src/test/renderer/specs/modules/ExpandType.NestedBehavior1.json index b6c548382..c78078397 100644 --- a/src/test/renderer/specs/modules/ExpandType.NestedBehavior1.json +++ b/src/test/renderer/specs/modules/ExpandType.NestedBehavior1.json @@ -36,11 +36,6 @@ } ] }, - { - "section.tsd-panel.tsd-comment": { - "div.tsd-comment.tsd-typography": [] - } - }, { "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", "props": { @@ -145,7 +140,13 @@ "href": "#aexpanded" }, "children": { - "span": "AExpanded" + "span": [ + "A", + { + "wbr": [] + }, + "Expanded" + ] } }, { diff --git a/src/test/renderer/specs/modules/ExpandType.json b/src/test/renderer/specs/modules/ExpandType.json index 5e9c63ab3..dc9662dd9 100644 --- a/src/test/renderer/specs/modules/ExpandType.json +++ b/src/test/renderer/specs/modules/ExpandType.json @@ -332,7 +332,13 @@ "href": "#aexpanded" }, "children": { - "span": "AExpanded" + "span": [ + "A", + { + "wbr": [] + }, + "Expanded" + ] } }, { @@ -341,7 +347,13 @@ "href": "#bexpanded" }, "children": { - "span": "BExpanded" + "span": [ + "B", + { + "wbr": [] + }, + "Expanded" + ] } }, { diff --git a/src/test/renderer/specs/modules/GH2982.json b/src/test/renderer/specs/modules/GH2982.json new file mode 100644 index 000000000..40c28d9dc --- /dev/null +++ b/src/test/renderer/specs/modules/GH2982.json @@ -0,0 +1,208 @@ +{ + "div.container.container-main": [ + { + "div.col-content": [ + { + "div.tsd-page-title": [ + { + "tag": "ul.tsd-breadcrumb", + "props": { + "aria-label": "Breadcrumb" + }, + "children": { + "li": { + "tag": "a", + "props": { + "href": "", + "aria-current": "page" + }, + "children": "GH2982" + } + } + }, + { + "h1": "Namespace GH2982" + } + ] + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Interfaces" + }, + "children": { + "h2": "Interfaces" + } + }, + { + "dl.tsd-member-summaries": [ + { + "dt.tsd-member-summary#tmxdatanode": { + "span.tsd-member-summary-name": [ + { + "tag": "a", + "props": { + "href": "../interfaces/GH2982.TMXDataNode.json" + }, + "children": "TMXDataNode" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#tmxdatanode", + "aria-label": "Permalink" + } + } + ] + } + }, + { + "dd.tsd-member-summary": [] + } + ] + } + ] + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Type Aliases" + }, + "children": { + "h2": "Type Aliases" + } + }, + { + "dl.tsd-member-summaries": [ + { + "dt.tsd-member-summary#tmxnode": { + "span.tsd-member-summary-name": [ + { + "tag": "a", + "props": { + "href": "../types/GH2982.TMXNode.json" + }, + "children": "TMXNode" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#tmxnode", + "aria-label": "Permalink" + } + } + ] + } + }, + { + "dd.tsd-member-summary": [] + } + ] + } + ] + } + ] + }, + { + "div.col-sidebar": { + "div.page-menu": { + "tag": "details.tsd-accordion.tsd-page-navigation", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary": { + "h3": "On This Page" + } + }, + { + "div.tsd-accordion-details": [ + { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Interfaces" + }, + "children": "Interfaces" + }, + { + "div": { + "tag": "a", + "props": { + "href": "#tmxdatanode" + }, + "children": { + "span": [ + "TMX", + { + "wbr": [] + }, + "Data", + { + "wbr": [] + }, + "Node" + ] + } + } + } + ] + }, + { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Type Aliases" + }, + "children": "Type Aliases" + }, + { + "div": { + "tag": "a", + "props": { + "href": "#tmxnode" + }, + "children": { + "span": [ + "TMX", + { + "wbr": [] + }, + "Node" + ] + } + } + } + ] + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/renderer/specs/modules/GH3007.json b/src/test/renderer/specs/modules/GH3007.json new file mode 100644 index 000000000..f409f158f --- /dev/null +++ b/src/test/renderer/specs/modules/GH3007.json @@ -0,0 +1,244 @@ +{ + "div.container.container-main": [ + { + "div.col-content": [ + { + "div.tsd-page-title": [ + { + "tag": "ul.tsd-breadcrumb", + "props": { + "aria-label": "Breadcrumb" + }, + "children": { + "li": { + "tag": "a", + "props": { + "href": "", + "aria-current": "page" + }, + "children": "GH3007" + } + } + }, + { + "h1": "Namespace GH3007" + } + ] + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Classes" + }, + "children": { + "h2": "Classes" + } + }, + { + "dl.tsd-member-summaries": [ + { + "dt.tsd-member-summary#dombase": { + "span.tsd-member-summary-name": [ + { + "tag": "a", + "props": { + "href": "../classes/GH3007.DOMBase.json" + }, + "children": "DOMBase" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#dombase", + "aria-label": "Permalink" + } + } + ] + } + }, + { + "dd.tsd-member-summary": [] + }, + { + "dt.tsd-member-summary#domclass": { + "span.tsd-member-summary-name": [ + { + "tag": "a", + "props": { + "href": "../classes/GH3007.DOMClass.json" + }, + "children": "DOMClass" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#domclass", + "aria-label": "Permalink" + } + } + ] + } + }, + { + "dd.tsd-member-summary": [] + } + ] + } + ] + }, + { + "tag": "details.tsd-panel-group.tsd-member-group.tsd-accordion", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Interfaces" + }, + "children": { + "h2": "Interfaces" + } + }, + { + "dl.tsd-member-summaries": [ + { + "dt.tsd-member-summary#domiterable": { + "span.tsd-member-summary-name": [ + { + "tag": "a", + "props": { + "href": "../interfaces/GH3007.DOMIterable.json" + }, + "children": "DOMIterable" + }, + { + "tag": "a.tsd-anchor-icon", + "props": { + "href": "#domiterable", + "aria-label": "Permalink" + } + } + ] + } + }, + { + "dd.tsd-member-summary": [] + } + ] + } + ] + } + ] + }, + { + "div.col-sidebar": { + "div.page-menu": { + "tag": "details.tsd-accordion.tsd-page-navigation", + "props": { + "open": true + }, + "children": [ + { + "summary.tsd-accordion-summary": { + "h3": "On This Page" + } + }, + { + "div.tsd-accordion-details": [ + { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Classes" + }, + "children": "Classes" + }, + { + "div": [ + { + "tag": "a", + "props": { + "href": "#dombase" + }, + "children": { + "span": [ + "DOM", + { + "wbr": [] + }, + "Base" + ] + } + }, + { + "tag": "a", + "props": { + "href": "#domclass" + }, + "children": { + "span": [ + "DOM", + { + "wbr": [] + }, + "Class" + ] + } + } + ] + } + ] + }, + { + "tag": "details.tsd-accordion.tsd-page-navigation-section", + "props": { + "open": true + }, + "children": [ + { + "tag": "summary.tsd-accordion-summary", + "props": { + "data-key": "section-Interfaces" + }, + "children": "Interfaces" + }, + { + "div": { + "tag": "a", + "props": { + "href": "#domiterable" + }, + "children": { + "span": [ + "DOM", + { + "wbr": [] + }, + "Iterable" + ] + } + } + } + ] + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/renderer/specs/types/ExpandType.AExpanded.json b/src/test/renderer/specs/types/ExpandType.AExpanded.json index 8b3b95b61..054638787 100644 --- a/src/test/renderer/specs/types/ExpandType.AExpanded.json +++ b/src/test/renderer/specs/types/ExpandType.AExpanded.json @@ -36,11 +36,6 @@ } ] }, - { - "section.tsd-panel.tsd-comment": { - "div.tsd-comment.tsd-typography": [] - } - }, { "div.tsd-signature": [ { @@ -61,7 +56,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -86,7 +81,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -111,7 +106,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -271,7 +266,7 @@ { "div.tsd-type-declaration": [ { - "h4": "Type declaration" + "h4": "Type Declaration" }, { "ul.tsd-parameters": { @@ -290,10 +285,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    A

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "A" + } } ] } diff --git a/src/test/renderer/specs/types/ExpandType.BExpanded.json b/src/test/renderer/specs/types/ExpandType.BExpanded.json index e8eff5cf4..146e2c691 100644 --- a/src/test/renderer/specs/types/ExpandType.BExpanded.json +++ b/src/test/renderer/specs/types/ExpandType.BExpanded.json @@ -56,7 +56,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -81,7 +81,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -106,7 +106,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -318,7 +318,7 @@ { "div.tsd-type-declaration": [ { - "h4": "Type declaration" + "h4": "Type Declaration" }, { "ul.tsd-parameters": { @@ -337,10 +337,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    B

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "B" + } } ] } diff --git a/src/test/renderer/specs/types/ExpandType.Expandable.json b/src/test/renderer/specs/types/ExpandType.Expandable.json index be39fc09a..9a12bb136 100644 --- a/src/test/renderer/specs/types/ExpandType.Expandable.json +++ b/src/test/renderer/specs/types/ExpandType.Expandable.json @@ -56,7 +56,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -185,10 +185,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    A

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "A" + } }, { "aside.tsd-sources": { diff --git a/src/test/renderer/specs/types/ExpandType.Expandable2.json b/src/test/renderer/specs/types/ExpandType.Expandable2.json index 1660b2262..5adbf8dbe 100644 --- a/src/test/renderer/specs/types/ExpandType.Expandable2.json +++ b/src/test/renderer/specs/types/ExpandType.Expandable2.json @@ -56,7 +56,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -185,10 +185,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    C

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "C" + } }, { "aside.tsd-sources": { diff --git a/src/test/renderer/specs/types/ExpandType.NestedBehavior1.AExpanded.json b/src/test/renderer/specs/types/ExpandType.NestedBehavior1.AExpanded.json index 678a54354..95b96b709 100644 --- a/src/test/renderer/specs/types/ExpandType.NestedBehavior1.AExpanded.json +++ b/src/test/renderer/specs/types/ExpandType.NestedBehavior1.AExpanded.json @@ -45,11 +45,6 @@ } ] }, - { - "section.tsd-panel.tsd-comment": { - "div.tsd-comment.tsd-typography": [] - } - }, { "div.tsd-signature": [ { @@ -70,7 +65,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -95,7 +90,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -120,7 +115,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -332,7 +327,7 @@ { "div.tsd-type-declaration": [ { - "h4": "Type declaration" + "h4": "Type Declaration" }, { "ul.tsd-parameters": { @@ -351,10 +346,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    B

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "B" + } } ] } diff --git a/src/test/renderer/specs/types/ExpandType.NestedBehavior1.AllExpanded.json b/src/test/renderer/specs/types/ExpandType.NestedBehavior1.AllExpanded.json index 69dd59a7a..df4424c35 100644 --- a/src/test/renderer/specs/types/ExpandType.NestedBehavior1.AllExpanded.json +++ b/src/test/renderer/specs/types/ExpandType.NestedBehavior1.AllExpanded.json @@ -65,7 +65,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -90,7 +90,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -115,7 +115,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -275,7 +275,7 @@ { "div.tsd-type-declaration": [ { - "h4": "Type declaration" + "h4": "Type Declaration" }, { "ul.tsd-parameters": { @@ -294,10 +294,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    A

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "A" + } } ] } @@ -359,7 +358,7 @@ { "div.tsd-type-declaration": [ { - "h4": "Type declaration" + "h4": "Type Declaration" }, { "ul.tsd-parameters": { @@ -378,10 +377,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    B

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "B" + } } ] } @@ -443,7 +441,7 @@ { "div.tsd-type-declaration": [ { - "h4": "Type declaration" + "h4": "Type Declaration" }, { "ul.tsd-parameters": { @@ -462,10 +460,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    C

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "C" + } } ] } diff --git a/src/test/renderer/specs/types/GH2982.TMXNode.json b/src/test/renderer/specs/types/GH2982.TMXNode.json new file mode 100644 index 000000000..31febec59 --- /dev/null +++ b/src/test/renderer/specs/types/GH2982.TMXNode.json @@ -0,0 +1,120 @@ +{ + "div.container.container-main": [ + { + "div.col-content": [ + { + "div.tsd-page-title": [ + { + "tag": "ul.tsd-breadcrumb", + "props": { + "aria-label": "Breadcrumb" + }, + "children": [ + { + "li": { + "tag": "a", + "props": { + "href": "../modules/GH2982.json" + }, + "children": "GH2982" + } + }, + { + "li": { + "tag": "a", + "props": { + "href": "", + "aria-current": "page" + }, + "children": "TMXNode" + } + } + ] + }, + { + "h1": "Type Alias TMXNode" + } + ] + }, + { + "div.tsd-signature": [ + { + "span.tsd-kind-type-alias": "TMXNode" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "span.tsd-signature-symbol": "{}" + }, + " ", + { + "span.tsd-signature-symbol": "&" + }, + " ", + { + "span.tsd-signature-symbol": "{" + }, + " ", + { + "span.tsd-kind-property": "base" + }, + { + "span.tsd-signature-symbol": ":" + }, + " ", + { + "tag": "a.tsd-signature-type.tsd-kind-type-parameter", + "props": { + "href": "#t" + }, + "children": "T" + }, + " ", + { + "span.tsd-signature-symbol": "}" + } + ] + }, + { + "section.tsd-panel": [ + { + "h4": "Type Parameters" + }, + { + "ul.tsd-type-parameter-list": { + "li": { + "span#t": { + "span.tsd-kind-type-parameter": "T" + } + } + } + } + ] + }, + { + "aside.tsd-sources": { + "ul": { + "li": [ + "Defined in ", + { + "tag": "a", + "props": { + "href": "gh2982.ts" + }, + "children": "gh2982.ts:1" + } + ] + } + } + } + ] + }, + { + "div.col-sidebar": { + "div.page-menu": [] + } + } + ] +} diff --git a/src/test/renderer/specs/types/Nested.json b/src/test/renderer/specs/types/Nested.json index 434656ec1..18f04453c 100644 --- a/src/test/renderer/specs/types/Nested.json +++ b/src/test/renderer/specs/types/Nested.json @@ -26,14 +26,11 @@ ] }, { - "section.tsd-panel.tsd-comment": [ - { - "div.tsd-comment.tsd-typography": "

    Type alias with nested properties

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "section.tsd-panel.tsd-comment": { + "div.tsd-comment.tsd-typography": { + "p": "Type alias with nested properties" } - ] + } }, { "div.tsd-signature": [ @@ -55,7 +52,7 @@ { "br": [] }, - "    ", + " ", { "tag": "a.tsd-kind-property", "props": { @@ -73,7 +70,7 @@ { "br": [] }, - "        ", + " ", { "span.tsd-kind-property": "anotherValue" }, @@ -90,7 +87,7 @@ { "br": [] }, - "        ", + " ", { "span.tsd-kind-property": "emptyObject" }, @@ -107,7 +104,7 @@ { "br": [] }, - "        ", + " ", { "span.tsd-kind-property": "moreOptions" }, @@ -139,7 +136,7 @@ { "br": [] }, - "        ", + " ", { "span.tsd-kind-property": "value" }, @@ -156,7 +153,7 @@ { "br": [] }, - "    ", + " ", { "span.tsd-signature-symbol": "}" }, @@ -181,7 +178,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:85" + "children": "index.ts:88" } ] } @@ -274,7 +271,7 @@ { "br": [] }, - "    ", + " ", { "span.tsd-kind-property": "anotherValue" }, @@ -291,7 +288,7 @@ { "br": [] }, - "    ", + " ", { "span.tsd-kind-property": "emptyObject" }, @@ -308,7 +305,7 @@ { "br": [] }, - "    ", + " ", { "span.tsd-kind-property": "moreOptions" }, @@ -340,7 +337,7 @@ { "br": [] }, - "    ", + " ", { "span.tsd-kind-property": "value" }, @@ -365,7 +362,7 @@ { "div.tsd-type-declaration": [ { - "h4": "Type declaration" + "h4": "Type Declaration" }, { "ul.tsd-parameters": [ @@ -388,10 +385,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    Another value

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "Another value" + } } ] }, @@ -444,10 +440,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    More options

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "More options" + } } ] }, @@ -470,10 +465,9 @@ ] }, { - "div.tsd-comment.tsd-typography": "

    Value

    \n" - }, - { - "div.tsd-comment.tsd-typography": [] + "div.tsd-comment.tsd-typography": { + "p": "Value" + } } ] } @@ -491,7 +485,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:86" + "children": "index.ts:89" } ] } diff --git a/src/test/renderer/specs/types/UnionComments.json b/src/test/renderer/specs/types/UnionComments.json index 470df7d50..aabbcc333 100644 --- a/src/test/renderer/specs/types/UnionComments.json +++ b/src/test/renderer/specs/types/UnionComments.json @@ -50,7 +50,7 @@ { "div.tsd-type-declaration": [ { - "h4": "Type declaration" + "h4": "Type Declaration" }, { "ul": [ @@ -60,7 +60,9 @@ "span.tsd-signature-type": "\"abc\"" }, { - "div.tsd-comment.tsd-typography": "

    Commentary on abc

    \n" + "div.tsd-comment.tsd-typography": { + "p": "Commentary on abc" + } } ] }, @@ -70,7 +72,9 @@ "span.tsd-signature-type": "\"def\"" }, { - "div.tsd-comment.tsd-typography": "

    Commentary on def

    \n" + "div.tsd-comment.tsd-typography": { + "p": "Commentary on def" + } } ] } @@ -88,7 +92,7 @@ "props": { "href": "index.ts" }, - "children": "index.ts:99" + "children": "index.ts:102" } ] } diff --git a/src/test/renderer/testRendererUtils.ts b/src/test/renderer/testRendererUtils.ts index bb501f703..14632fd4b 100644 --- a/src/test/renderer/testRendererUtils.ts +++ b/src/test/renderer/testRendererUtils.ts @@ -1,10 +1,11 @@ import { type Reflection, resetReflectionID } from "#models"; -import { loadTestHighlighter } from "#node-utils"; +import { HtmlAttributeParser, loadTestHighlighter, ParserState } from "#node-utils"; import { rm } from "node:fs/promises"; import { DefaultTheme, KindRouter, PageEvent, PageKind, type RenderTemplate } from "../../lib/output/index.js"; import { type JsxChildren, type JsxElement, JsxFragment } from "../../lib/utils-common/jsx.elements.js"; import { Raw } from "../../lib/utils-common/jsx.js"; import { getConverter2App, getConverter2Project } from "../programs.js"; +import { assert } from "#utils"; function shouldIgnoreElement(el: JsxElement) { switch (el.tag) { @@ -46,13 +47,127 @@ function collapseStrings(data: any[]): unknown { return data; } +// This is a very hacky html parser only intended to handle output from markdown-it +// for inclusion in the renderer specs. Don't use it for anything that requires actual +// security. +function parseHtmlToJsxElement(html: string): JsxChildren[] { + const stack: JsxElement[] = []; + const output: JsxChildren[] = []; + let pos = 0; + let last = 0; + + function currentChildList() { + if (stack.length) { + return stack[stack.length - 1].children; + } + return output; + } + + function skipWs() { + while (pos < html.length && /\s/.test(html[pos])) ++pos; + } + + function takeWord() { + const start = pos; + while (pos < html.length && /[a-z0-9-]/i.test(html[pos])) ++pos; + return html.slice(start, pos); + } + + function startTag() { + assert(html[pos] === "<"); + + ++pos; + skipWs(); + + const tag = takeWord(); + + const parser = new HtmlAttributeParser(html, pos); + const props: Record = {}; + + while (parser.state !== ParserState.END) { + if (parser.state === ParserState.BeforeAttributeValue) { + parser.step(); + props[parser.currentAttributeName] = parser.currentAttributeValue; + } else { + parser.step(); + } + } + + pos = parser.pos - 1; + + const element = { + tag, + props, + children: [], + }; + + if (html[pos] === "/") { + // self closing tag + currentChildList().push(element); + ++pos; + skipWs(); + } else { + currentChildList().push(element); + stack.push(element); + } + + assert(html[pos] === ">"); + ++pos; + last = pos; + } + + function endTag() { + assert(html[pos] === "<" && html[pos + 1] === "/"); + pos += 2; + skipWs(); + + const tag = takeWord(); + if (stack.length === 0 || stack[stack.length - 1].tag !== tag) { + throw new Error(`Invalid HTML, failed to match end tag: ${tag}`); + } + stack.pop(); + + skipWs(); + assert(html[pos] === ">"); + ++pos; + last = pos; + } + + function saveContent() { + if (pos !== last) { + const content = html.slice(last, pos); + currentChildList().push(content.trim()); + } + last = pos; + } + + while (pos < html.length) { + switch (html[pos]) { + case "<": + saveContent(); + if (html[pos + 1] == "/") { + endTag(); + } else { + startTag(); + } + break; + default: + ++pos; + break; + } + } + + saveContent(); + return output; +} + function renderElementToSnapshot(element: JsxChildren): unknown { - if (!element || typeof element === "boolean") { - return ""; + if (typeof element === "string" || typeof element === "number" || typeof element === "bigint") { + return element.toString().replaceAll("\u00a0", " "); } - if (typeof element === "string" || typeof element === "number") { - return element.toString(); + if (!element || typeof element === "boolean") { + return ""; } if (Array.isArray(element)) { @@ -67,7 +182,7 @@ function renderElementToSnapshot(element: JsxChildren): unknown { if (typeof tag === "function") { if (tag === Raw) { - return String((props as any).html); + return renderElementToSnapshot(parseHtmlToJsxElement(String((props as any).html))); } if (tag === JsxFragment) { return collapseStrings(children.flatMap(renderElementToSnapshot).filter(Boolean)); @@ -155,10 +270,10 @@ export async function buildRendererSpecs(specPath: string) { // Unfortunate not to set this in typedoc.json for converter2, but plenty // of tests expect to test the default option, not this. app2.options.setValue("categorizeByGroup", true); + app2.options.setValue("readme", "src/test/converter2/renderer/renderer-readme.md"); resetReflectionID(); const project = getConverter2Project(["renderer"], "."); - project.readme = [{ kind: "text", text: "Readme text" }]; await app2.generateDocs(project, specPath); await rm(`${specPath}/assets`, { recursive: true }); await rm(`${specPath}/.nojekyll`); diff --git a/src/test/slow/internationalization-usage.test.ts b/src/test/slow/internationalization-usage.test.ts index d1e4efd38..b669f03f2 100644 --- a/src/test/slow/internationalization-usage.test.ts +++ b/src/test/slow/internationalization-usage.test.ts @@ -9,7 +9,8 @@ describe("Internationalization", () => { it("Does not include strings in translatable object which are unused", () => { const options = new Options(); const tsconfigReader = new TSConfigReader(); - tsconfigReader.read(options, new Logger(), process.cwd()); + const logger = new Logger(); + tsconfigReader.read(options, logger, process.cwd()); const defaultLocaleTs = join( fileURLToPath(import.meta.url), @@ -26,7 +27,7 @@ describe("Internationalization", () => { ); }, getCurrentDirectory: () => process.cwd(), - getCompilationSettings: () => options.getCompilerOptions(), + getCompilationSettings: () => options.getCompilerOptions(logger), getDefaultLibFileName: (opts) => ts.getDefaultLibFilePath(opts), fileExists: ts.sys.fileExists, readFile: ts.sys.readFile, diff --git a/src/test/utils-common/enum.test.ts b/src/test/utils-common/enum.test.ts index 1c58bc503..60fab2397 100644 --- a/src/test/utils-common/enum.test.ts +++ b/src/test/utils-common/enum.test.ts @@ -1,9 +1,45 @@ -import { ok } from "assert"; +import { deepStrictEqual as equal, ok } from "assert"; import { ReflectionKind } from "../../lib/models/index.js"; -import { getEnumKeys } from "#utils"; +import { debugFlags, getEnumFlags, getEnumKeys, hasAllFlags, hasAnyFlag, removeFlag } from "#utils"; describe("Enum utils", () => { - it("Should be able to get enum keys", () => { + it("getEnumFlags", () => { + const keys = getEnumFlags(123); + equal(keys, [1, 2, 8, 16, 32, 64]); + }); + + it("removeFlag", () => { + equal(removeFlag(3, 2), 1); + equal(removeFlag(3, 1), 2); + equal(removeFlag(3, 4), 3); + }); + + it("hasAllFlags", () => { + equal(hasAllFlags(3, 1), true); + equal(hasAllFlags(3, 1 | 2), true); + equal(hasAllFlags(3, 1 | 4), false); + }); + + it("hasAnyFlag", () => { + equal(hasAnyFlag(3, 1), true); + equal(hasAnyFlag(3, 1 | 2), true); + equal(hasAnyFlag(3, 1 | 4), true); + equal(hasAnyFlag(3, 4), false); + }); + + it("debugFlags", () => { + enum Flags { + A = 1, + B = 2, + C = 4, + } + equal(debugFlags(Flags, Flags.A), ["A"]); + equal(debugFlags(Flags, Flags.A | Flags.B), ["A", "B"]); + equal(debugFlags(Flags, Flags.A | Flags.C), ["A", "C"]); + equal(debugFlags(Flags, 0), []); + }); + + it("getEnumKeys", () => { const keys = getEnumKeys(ReflectionKind); ok(keys.includes("Project")); ok(!keys.includes("SignatureContainer")); diff --git a/src/test/utils-common/jsx.test.tsx b/src/test/utils-common/jsx.test.tsx index eef2abe8a..a642c0a31 100644 --- a/src/test/utils-common/jsx.test.tsx +++ b/src/test/utils-common/jsx.test.tsx @@ -1,9 +1,13 @@ import { deepStrictEqual as equal } from "assert"; import { JSX } from "#utils"; - -const renderElement = JSX.renderElement; +import { renderElement, renderElementToText } from "../../lib/utils-common/jsx.js"; describe("JSX", () => { + it("Handles null/undefined", () => { + equal(renderElement(null), ``); + equal(renderElement(undefined), ``); + }); + it("Works with basic case", () => { const element = (
    @@ -70,6 +74,9 @@ describe("JSX", () => { it("Supports for injecting HTML", () => { equal(renderElement(), "foo"); + + // This is should never be used in common usage, but it shouldn't break + equal(JSX.Raw({ html: "" }), null); }); it("Supports SVG elements", () => { @@ -89,4 +96,46 @@ describe("JSX", () => { const quot = `test"quote`; equal(renderElement(
    ), `
    `); }); + + it("Handles non-string attributes", () => { + equal(renderElement(
    ), `
    `); + }); + + it("Handles void elements", () => { + equal(renderElement(
    ), `
    `); + }); + + it("Handles boolean children", () => { + equal(renderElement({true}{false}), ``); + }); + + it("Handles zero", () => { + equal(renderElement({0}), `0`); + }); +}); + +describe("JSX.renderElementToText", () => { + it("Handles null/undefined", () => { + equal(renderElementToText(null), ``); + equal(renderElementToText(undefined), ``); + }); + + it("Handles zero", () => { + equal(renderElementToText({0}), `0`); + }); + + it("Handles boolean children", () => { + equal(renderElementToText({true}{false}), ``); + }); + + it("Handles Raw elements", () => { + equal(renderElementToText(), ``); + }); + + it("Handles components", () => { + function Component() { + return hi; + } + equal(renderElementToText(), `hi`); + }); }); diff --git a/src/test/utils-common/map.test.ts b/src/test/utils-common/map.test.ts new file mode 100644 index 000000000..4eabb5d01 --- /dev/null +++ b/src/test/utils-common/map.test.ts @@ -0,0 +1,114 @@ +import { deepStrictEqual as equal, ok } from "assert"; +import { DefaultMap, StableKeyMap } from "#utils"; + +describe("DefaultMap", () => { + it("Creates entries if they do not exist", () => { + const map = new DefaultMap(() => 123); + + equal(map.get("a"), 123); + map.set("b", 5); + equal(map.get("b"), 5); + equal(map.getNoInsert("c"), undefined); + }); +}); + +describe("StableKeyMap", () => { + interface StableKeyed { + getStableKey(): string; + } + const a = { + id: 1, + getStableKey() { + return "a"; + }, + }; + const a2 = { + id: 2, + getStableKey() { + return "a"; + }, + }; + + it("Inserts objects via a stable key", () => { + const map = new StableKeyMap(); + + equal(map.size, 0); + map.set(a, 1); + equal(map.size, 1); + equal(map.get(a), 1); + equal(map.get(a2), 1); + }); + + it("Supports clear()", () => { + const map = new StableKeyMap(); + map.set(a, 1); + map.clear(); + equal(map.size, 0); + }); + + it("Supports delete()", () => { + const map = new StableKeyMap(); + map.set(a, 1); + equal(map.delete(a2), true); + equal(map.size, 0); + }); + + it("Supports forEach()", () => { + const map = new StableKeyMap(); + map.set(a, 1); + let called = false; + map.forEach((value, key, map2) => { + called = true; + equal(value, 1); + ok(key === a); + ok(map === map2); + }); + ok(called); + }); + + it("Supports entries()", () => { + const map = new StableKeyMap(); + map.set(a, 1); + let called = false; + for (const [key, value] of map.entries()) { + called = true; + equal(value, 1); + ok(key === a); + } + ok(called); + }); + + it("Supports keys()", () => { + const map = new StableKeyMap(); + map.set(a, 1); + let called = false; + for (const key of map.keys()) { + called = true; + ok(key === a); + } + ok(called); + }); + + it("Supports values()", () => { + const map = new StableKeyMap(); + map.set(a, 1); + let called = false; + for (const value of map.values()) { + called = true; + equal(value, 1); + } + ok(called); + }); + + it("Supports [Symbol.iterator]", () => { + const map = new StableKeyMap(); + map.set(a, 1); + let called = false; + for (const [key, value] of map) { + called = true; + equal(value, 1); + ok(key === a); + } + ok(called); + }); +}); diff --git a/src/test/utils.ts b/src/test/utils.ts index 409e65795..40b801487 100644 --- a/src/test/utils.ts +++ b/src/test/utils.ts @@ -60,7 +60,7 @@ export function querySig( ): SignatureReflection { const decl = query(project, name); ok( - decl.signatures?.length ?? 0 > index, + (decl.signatures?.length ?? 0) > index, `Reflection "${name}" does not contain signature`, ); return decl.signatures![index]; @@ -108,7 +108,7 @@ export function equalKind(refl: Reflection, kind: ReflectionKind) { equal( refl.kind, kind, - `Expected ${ReflectionKind[kind]} but got ${ReflectionKind[refl.kind]}`, + `Expected ${ReflectionKind[kind]} but got ${ReflectionKind[refl.kind]} for ${refl.getFullName()}`, ); } diff --git a/src/test/utils/general.test.ts b/src/test/utils/general.test.ts index 108ea73af..b9cbeaa06 100644 --- a/src/test/utils/general.test.ts +++ b/src/test/utils/general.test.ts @@ -1,27 +1,39 @@ -import { deepStrictEqual as equal } from "assert/strict"; -import { dedent } from "#utils"; +import { deepStrictEqual as equal, doesNotThrow, throws } from "assert/strict"; +import { assert, assertNever, dedent } from "#utils"; -describe("Dedent test helper", () => { - it("Works on empty string", () => { +describe("general.ts", () => { + it("dedent works on empty string", () => { equal(dedent(""), ""); }); - it("Works with indented text", () => { + it("dedent works with indented text", () => { equal( dedent(` - Text here - `), + Text here + `), "Text here", ); }); - it("Works with multiple lines", () => { + it("dedent works with multiple lines", () => { equal( dedent(` - Text here - More indented - `), + Text here + More indented + `), "Text here\n More indented", ); }); + + it("assertNever", () => { + throws( + () => assertNever("x" as never), + new Error(`Expected handling to cover all possible cases, but it didn't cover: "x"`), + ); + }); + + it("assert", () => { + doesNotThrow(() => assert(true)); + throws(() => assert(false), new Error("Assertion failed")); + }); }); diff --git a/src/test/utils/options/declaration.test.ts b/src/test/utils/options/declaration.test.ts index c61ae72ce..ef0252fab 100644 --- a/src/test/utils/options/declaration.test.ts +++ b/src/test/utils/options/declaration.test.ts @@ -200,7 +200,10 @@ describe("Options - conversions", () => { convert(["12,3"], optionWithType(ParameterType.Array), ""), ["12,3"], ); - equal(convert(true, optionWithType(ParameterType.Array), ""), []); + throws( + () => convert(true, optionWithType(ParameterType.Array), ""), + new Error("The 'test' option must be set to an array of strings"), + ); equal( convert("/,a", optionWithType(ParameterType.PathArray), ""), @@ -214,9 +217,9 @@ describe("Options - conversions", () => { ), [normalizePath(resolve("/foo"))], ); - equal( - convert(true, optionWithType(ParameterType.PathArray), ""), - [], + throws( + () => convert(true, optionWithType(ParameterType.PathArray), ""), + new Error("The 'test' option must be set to an array of strings"), ); equal( @@ -231,9 +234,40 @@ describe("Options - conversions", () => { ), ["a,b"], ); + throws( + () => convert(true, optionWithType(ParameterType.ModuleArray), ""), + new Error("The 'test' option must be set to an array of strings"), + ); + + equal( + convert("a,b", optionWithType(ParameterType.PluginArray), ""), + ["a,b"], + ); equal( - convert(true, optionWithType(ParameterType.ModuleArray), ""), - [], + convert( + ["a,b"], + optionWithType(ParameterType.PluginArray), + "", + ), + ["a,b"], + ); + const fn = () => {}; + equal( + convert( + ["a", fn], + optionWithType(ParameterType.PluginArray), + "", + ), + ["a", fn], + ); + throws( + () => convert(true, optionWithType(ParameterType.PluginArray), ""), + new Error("The 'test' option must be set to an array of strings/functions"), + ); + + throws( + () => convert(true, optionWithType(ParameterType.GlobArray), ""), + new Error("The 'test' option must be set to an array of strings"), ); }); @@ -248,6 +282,17 @@ describe("Options - conversions", () => { ); }); + it("PluginArray is resolved if relative", () => { + equal( + convert( + ["./foo"], + optionWithType(ParameterType.PluginArray), + "", + ), + [normalizePath(join(process.cwd(), "foo"))], + ); + }); + it("Validates array options", () => { const declaration: ArrayDeclarationOption = { name: "test", @@ -511,6 +556,21 @@ describe("Options - default values", () => { ); }); + it("PluginArray", () => { + equal( + getDefaultValue(getDeclaration(ParameterType.PluginArray, void 0)), + [], + ); + equal( + getDefaultValue(getDeclaration(ParameterType.PluginArray, ["a"])), + ["a"], + ); + equal( + getDefaultValue(getDeclaration(ParameterType.PluginArray, ["./a"])), + [normalizePath(resolve("./a"))], + ); + }); + it("GlobArray", () => { equal( getDefaultValue(getDeclaration(ParameterType.GlobArray, void 0)), diff --git a/src/test/utils/options/default-options.test.ts b/src/test/utils/options/default-options.test.ts index 88bf20f51..8b9ab552c 100644 --- a/src/test/utils/options/default-options.test.ts +++ b/src/test/utils/options/default-options.test.ts @@ -131,6 +131,172 @@ describe("Default Options", () => { }); }); + describe("locales", () => { + it("Should disallow non-objects", () => { + throws(() => opts.setValue("locales", null as never)); + }); + + it("Should disallow objects containing non-objects", () => { + throws(() => opts.setValue("locales", { test: false } as never)); + }); + + it("Should disallow locales containing non-strings", () => { + throws(() => opts.setValue("locales", { test: { key: false } } as never)); + }); + + it("Should allow valid locale shapes", () => { + doesNotThrow(() => opts.setValue("locales", { test: { key: "hi" } })); + }); + }); + + describe("packageOptions", () => { + it("Should disallow non-objects", () => { + throws(() => opts.setValue("packageOptions", null as never)); + }); + + it("Should allow objects", () => { + doesNotThrow(() => opts.setValue("packageOptions", { test: false } as never)); + }); + }); + + describe("blockTags", () => { + it("Should disallow non-tags", () => { + throws(() => opts.setValue("blockTags", ["@bad_tag"])); + throws(() => opts.setValue("blockTags", ["@2bad"])); + }); + + it("Should allow tags", () => { + doesNotThrow(() => opts.setValue("blockTags", ["@good"])); + doesNotThrow(() => opts.setValue("blockTags", ["@good2"])); + doesNotThrow(() => opts.setValue("blockTags", ["@Good"])); + doesNotThrow(() => opts.setValue("blockTags", ["@good-tag"])); + }); + }); + + describe("excludeNotDocumentedKinds", () => { + it("Should disallow invalid kind strings", () => { + throws(() => opts.setValue("excludeNotDocumentedKinds", ["InvalidKind"] as never)); + }); + + it("Should disallow disallowed kind strings", () => { + throws(() => opts.setValue("excludeNotDocumentedKinds", ["Project"] as never)); + }); + + it("Should kinds", () => { + doesNotThrow(() => opts.setValue("excludeNotDocumentedKinds", ["Variable"])); + }); + }); + + describe("externalSymbolLinkMappings", () => { + it("Should disallow non-objects", () => { + throws(() => opts.setValue("externalSymbolLinkMappings", null as never)); + }); + + it("Should disallow objects containing non-objects", () => { + throws(() => opts.setValue("externalSymbolLinkMappings", { test: false } as never)); + }); + + it("Should disallow mappings containing non-strings", () => { + throws(() => opts.setValue("externalSymbolLinkMappings", { test: { key: false } } as never)); + }); + + it("Should allow valid mapping shapes", () => { + doesNotThrow(() => opts.setValue("externalSymbolLinkMappings", { test: { key: "hi" } })); + }); + }); + + describe("outputs", () => { + it("Should disallow non-arrays", () => { + throws(() => opts.setValue("outputs", null as never)); + }); + + it("Should arrays of invalid shape", () => { + throws(() => opts.setValue("outputs", [{}] as never)); + }); + + it("Should allow valid shapes", () => { + doesNotThrow(() => opts.setValue("outputs", [{ name: "html", path: "out" }])); + }); + }); + + describe("highlightLanguages", () => { + it("Should disallow non-arrays", () => { + throws(() => opts.setValue("highlightLanguages", null as never)); + }); + + it("Should disallow arrays containing unknown languages", () => { + throws(() => opts.setValue("highlightLanguages", ["notASupportedLanguage"] as never)); + }); + + it("Should allow valid languages", () => { + doesNotThrow(() => opts.setValue("highlightLanguages", ["typescript"])); + }); + }); + + describe("markdownItLoader", () => { + it("Should disallow non-functions", () => { + throws(() => opts.setValue("markdownItLoader", null as never)); + }); + + it("Should allow functions", () => { + doesNotThrow(() => opts.setValue("markdownItLoader", () => {})); + }); + }); + + describe("favicon", () => { + it("Should disallow paths with an unknown extension", () => { + throws(() => opts.setValue("favicon", "test.txt")); + }); + + it("Should allow remote paths with an unknown extension", () => { + doesNotThrow(() => opts.setValue("favicon", "https://example.com/test.txt")); + }); + + it("Should allow paths with a known extension", () => { + doesNotThrow(() => opts.setValue("favicon", "test.png")); + }); + }); + + describe("hostedBaseurl", () => { + it("Should disallow non-http urls", () => { + throws(() => opts.setValue("hostedBaseUrl", "test")); + throws(() => opts.setValue("hostedBaseUrl", "ftp://test")); + }); + + it("Should allow http and https urls", () => { + doesNotThrow(() => opts.setValue("hostedBaseUrl", "http://example.com/")); + doesNotThrow(() => opts.setValue("hostedBaseUrl", "https://example.com/")); + }); + }); + + describe("visibilityFilters", () => { + it("Should disallow non-objects", () => { + throws(() => opts.setValue("visibilityFilters", "test" as never)); + }); + + it("Should disallow objects with an invalid key", () => { + throws(() => opts.setValue("visibilityFilters", { bad: true } as never)); + }); + + it("Should disallow objects with a non-boolean values", () => { + throws(() => opts.setValue("visibilityFilters", { private: "bad" } as never)); + }); + + it("Should allow objects with a valid shape", () => { + doesNotThrow(() => opts.setValue("visibilityFilters", { private: true })); + doesNotThrow(() => opts.setValue("visibilityFilters", { "@test": true })); + }); + }); + + describe("kindSortOrder", () => { + it("Should disallow non-kind strings", () => { + throws(() => opts.setValue("kindSortOrder", ["Bad"] as never)); + }); + it("Should allow valid sort orders", () => { + doesNotThrow(() => opts.setValue("kindSortOrder", ["Accessor"])); + }); + }); + it("Package Options Documentation", () => { const allOptions = opts .getDeclarations() diff --git a/src/test/utils/options/options.test.ts b/src/test/utils/options/options.test.ts index f58134f57..3366d851d 100644 --- a/src/test/utils/options/options.test.ts +++ b/src/test/utils/options/options.test.ts @@ -3,6 +3,8 @@ import { type MapDeclarationOption, type NumberDeclarationOption, Option } from import { deepStrictEqual as equal, throws } from "assert"; import type { DeclarationOption, EmitStrategy } from "../../../lib/utils/options/index.js"; import { LogLevel } from "#utils"; +import { TestLogger } from "../../TestLogger.js"; +import ts from "typescript"; describe("Options", () => { let options: Options & { @@ -180,6 +182,32 @@ describe("Options", () => { throws(() => options.reset("thisOptionDoesNotExist" as never)); }); + + it("Handles tsconfig overrides which aren't strings, #3000", () => { + const logger = new TestLogger(); + const options = new Options(); + + options.setValue("compilerOptions", { + moduleResolution: "bundler", + }); + + const compilerOptions = options.getCompilerOptions(logger); + equal(compilerOptions.moduleResolution, ts.ModuleResolutionKind.Bundler); + }); + + it("Handles tsconfig overrides which are invalid, #3000", () => { + const logger = new TestLogger(); + const options = new Options(); + + options.setValue("compilerOptions", { + moduleResolution: "bad", + }); + + const compilerOptions = options.getCompilerOptions(logger); + equal(compilerOptions.moduleResolution, undefined); + + logger.expectMessage("error: Failed to apply compilerOptions overrides: *"); + }); }); describe("Option", () => { diff --git a/src/test/utils/options/readers/tsconfig.test.ts b/src/test/utils/options/readers/tsconfig.test.ts index 5bd666e81..f49cbe42d 100644 --- a/src/test/utils/options/readers/tsconfig.test.ts +++ b/src/test/utils/options/readers/tsconfig.test.ts @@ -162,7 +162,7 @@ describe("Options - TSConfigReader", () => { compilerOptions: { strict: true }, }); await readWithProject(project); - equal(options.getCompilerOptions().strict, true); + equal(options.getCompilerOptions(logger).strict, true); }); async function testTsdoc(tsdoc: object, cb?: () => void, reset = true) { diff --git a/src/test/utils/plugins.test.ts b/src/test/utils/plugins.test.ts index 31f5962ba..0199d5ab6 100644 --- a/src/test/utils/plugins.test.ts +++ b/src/test/utils/plugins.test.ts @@ -1,8 +1,9 @@ import { tempdirProject } from "@typestrong/fs-fixture-builder"; -import type { Application } from "../../index.js"; +import { type Application, normalizePath } from "../../index.js"; import { loadPlugins } from "../../lib/utils/plugins.js"; import { TestLogger } from "../TestLogger.js"; import { join, resolve } from "path"; +import { deepStrictEqual as equal } from "assert/strict"; describe("loadPlugins", () => { let logger: TestLogger; @@ -20,7 +21,7 @@ describe("loadPlugins", () => { project.addFile("index.js", "exports.load = function load() {}"); project.write(); - const plugin = resolve(project.cwd, "index.js"); + const plugin = normalizePath(resolve(project.cwd, "index.js")); await loadPlugins(fakeApp, [plugin]); logger.expectMessage(`info: Loaded plugin ${plugin}`); }); @@ -31,10 +32,12 @@ describe("loadPlugins", () => { type: "commonjs", main: "index.js", }); - const plugin = project.addFile( - "index.js", - "exports.load = function load() {}", - ).path; + const plugin = normalizePath( + project.addFile( + "index.js", + "exports.load = function load() {}", + ).path, + ); project.write(); await loadPlugins(fakeApp, [plugin]); @@ -50,11 +53,21 @@ describe("loadPlugins", () => { project.addFile("index.js", "export function load() {}"); project.write(); - const plugin = join(resolve(project.cwd), "index.js"); + const plugin = normalizePath(join(resolve(project.cwd), "index.js")); await loadPlugins(fakeApp, [plugin]); logger.expectMessage(`info: Loaded plugin ${plugin}`); }); + it("Should support loading a function plugin", async () => { + let called = false as boolean; + function testFn() { + called = true; + } + await loadPlugins(fakeApp, [testFn]); + logger.expectMessage(`info: Loaded plugin testFn`); + equal(called, true); + }); + it("Should handle errors when requiring plugins", async () => { using project = tempdirProject(); project.addJsonFile("package.json", { @@ -64,7 +77,7 @@ describe("loadPlugins", () => { project.addFile("index.js", "throw Error('bad')"); project.write(); - const plugin = join(resolve(project.cwd), "index.js"); + const plugin = normalizePath(join(resolve(project.cwd), "index.js")); await loadPlugins(fakeApp, [plugin]); logger.expectMessage(`error: The plugin ${plugin} could not be loaded`); }); @@ -81,7 +94,7 @@ describe("loadPlugins", () => { ); project.write(); - const plugin = join(resolve(project.cwd), "index.js"); + const plugin = normalizePath(join(resolve(project.cwd), "index.js")); await loadPlugins(fakeApp, [plugin]); logger.expectMessage(`error: The plugin ${plugin} could not be loaded`); }); @@ -95,7 +108,7 @@ describe("loadPlugins", () => { project.addFile("index.js", ""); project.write(); - const plugin = join(resolve(project.cwd), "index.js"); + const plugin = normalizePath(join(resolve(project.cwd), "index.js")); await loadPlugins(fakeApp, [plugin]); logger.expectMessage( `error: Invalid structure in plugin ${plugin}, no load function found`, diff --git a/src/test/validation.test.ts b/src/test/validation.test.ts index cba32af1d..905672c25 100644 --- a/src/test/validation.test.ts +++ b/src/test/validation.test.ts @@ -2,73 +2,58 @@ import { ok } from "assert"; import { join } from "path"; import { validateDocumentation } from "../lib/validation/documentation.js"; import { validateExports } from "../lib/validation/exports.js"; -import { getConverter2App, getConverter2Program } from "./programs.js"; +import { getConverter2App, getConverter2Program, getConverter2Project } from "./programs.js"; import { TestLogger } from "./TestLogger.js"; import { fileURLToPath } from "url"; +import { validateMergeModuleWith } from "../lib/validation/unusedMergeModuleWith.js"; -function convertValidationFile(file: string) { - const app = getConverter2App(); - const program = getConverter2Program(); - const sourceFile = program.getSourceFile( - join(fileURLToPath(import.meta.url), "../converter2/validation", file), - ); +function convertValidationFile(...files: [string, ...string[]]) { + return getConverter2Project(files, "validation"); +} - ok(sourceFile, "Specified source file does not exist."); +describe("validateExports", () => { + function expectWarning( + typeName: string, + file: string, + referencingName: string, + intentionallyNotExported: readonly string[] = [], + ) { + const project = convertValidationFile(file); - const project = app.converter.convert([ - { - displayName: "validation", - program, - sourceFile, - }, - ]); + const logger = new TestLogger(); + validateExports(project, logger, intentionallyNotExported); - return project; -} + logger.expectMessage( + `warn: ${typeName}, defined in */${file}, is referenced by ${referencingName} but not included in the documentation`, + ); + } -function expectWarning( - typeName: string, - file: string, - referencingName: string, - intentionallyNotExported: readonly string[] = [], -) { - const project = convertValidationFile(file); + function expectNoWarning( + file: string, + intentionallyNotExported: readonly string[] = [], + ) { + const app = getConverter2App(); + const program = getConverter2Program(); + const sourceFile = program.getSourceFile( + join(fileURLToPath(import.meta.url), "../converter2/validation", file), + ); - const logger = new TestLogger(); - validateExports(project, logger, intentionallyNotExported); + ok(sourceFile, "Specified source file does not exist."); - logger.expectMessage( - `warn: ${typeName}, defined in */${file}, is referenced by ${referencingName} but not included in the documentation`, - ); -} + const project = app.converter.convert([ + { + displayName: "validation", + program, + sourceFile, + }, + ]); -function expectNoWarning( - file: string, - intentionallyNotExported: readonly string[] = [], -) { - const app = getConverter2App(); - const program = getConverter2Program(); - const sourceFile = program.getSourceFile( - join(fileURLToPath(import.meta.url), "../converter2/validation", file), - ); - - ok(sourceFile, "Specified source file does not exist."); - - const project = app.converter.convert([ - { - displayName: "validation", - program, - sourceFile, - }, - ]); - - const logger = new TestLogger(); - - validateExports(project, logger, intentionallyNotExported); - logger.expectNoOtherMessages(); -} + const logger = new TestLogger(); + + validateExports(project, logger, intentionallyNotExported); + logger.expectNoOtherMessages(); + } -describe("validateExports", () => { it("Should warn if a variable type is missing", () => { expectWarning("Foo", "variable.ts", "foo"); }); @@ -224,3 +209,27 @@ describe("validateDocumentation", () => { logger.expectNoOtherMessages(); }); }); + +describe("validateMergeModuleWith", () => { + it("Should warn if the project has a @mergeModuleWith tag", () => { + const project = convertValidationFile("unusedMergeModuleWith.ts"); + + const logger = new TestLogger(); + validateMergeModuleWith(project, logger); + + logger.expectMessage( + `warn: has a @mergeModuleWith tag which could not be resolved`, + ); + }); + + it("Should warn if the project's module has a @mergeModuleWith tag", () => { + const project = convertValidationFile("unusedMergeModuleWith.ts", "return.ts"); + + const logger = new TestLogger(); + validateMergeModuleWith(project, logger); + + logger.expectMessage( + `warn: unusedMergeModuleWith has a @mergeModuleWith tag which could not be resolved`, + ); + }); +}); diff --git a/static/style.css b/static/style.css index 7c3fbdb90..5ba5a2a90 100644 --- a/static/style.css +++ b/static/style.css @@ -504,15 +504,8 @@ body { background: var(--color-background); font-family: - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - "Noto Sans", - Helvetica, - Arial, - sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji"; + -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; color: var(--color-text); margin: 0; diff --git a/tsdoc.json b/tsdoc.json index 4fd1c034d..07d3e835c 100644 --- a/tsdoc.json +++ b/tsdoc.json @@ -146,6 +146,11 @@ "syntaxKind": "block", "allowMultiple": true }, + { + "tagName": "@this", + "syntaxKind": "block", + "allowMultiple": false + }, { "tagName": "@linkcode", "syntaxKind": "inline", @@ -237,6 +242,10 @@ { "tagName": "@useDeclaredType", "syntaxKind": "modifier" + }, + { + "tagName": "@sortStrategy", + "syntaxKind": "block" } ] }

    Link to this doc: link